    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.tftp;
    019
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.InterruptedIOException;
    023import java.io.OutputStream;
    024import java.net.InetAddress;
    025import java.net.SocketException;
    026import java.net.UnknownHostException;
    027import org.apache.commons.net.io.FromNetASCIIOutputStream;
    028import org.apache.commons.net.io.ToNetASCIIInputStream;
    029
    030/***
    031 * The TFTPClient class encapsulates all the aspects of the TFTP protocol
    032 * necessary to receive and send files through TFTP.  It is derived from
    033 * the {@link org.apache.commons.net.tftp.TFTP} because
    034 * it is more convenient than using aggregation, and as a result exposes
    035 * the same set of methods to allow you to deal with the TFTP protocol
    036 * directly.  However, almost every user should only be concerend with the
    037 * the {@link org.apache.commons.net.DatagramSocketClient#open  open() },
    038 * {@link org.apache.commons.net.DatagramSocketClient#close  close() },
    039 * {@link #sendFile  sendFile() }, and
    040 * {@link #receiveFile  receiveFile() } methods.  Additionally, the
    041 * {@link #setMaxTimeouts  setMaxTimeouts() } and
    042 * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() }
    043 *  methods may be of importance for performance
    044 * tuning.
    045 * <p>
    046 * Details regarding the TFTP protocol and the format of TFTP packets can
    047 * be found in RFC 783.  But the point of these classes is to keep you
    048 * from having to worry about the internals.
    049 *
    050 *
    051 * @see TFTP
    052 * @see TFTPPacket
    053 * @see TFTPPacketException
    054 ***/
    055
    056public class TFTPClient extends TFTP
    057{
    058    /***
    059     * The default number of times a receive attempt is allowed to timeout
    060     * before ending attempts to retry the receive and failing.  The default
    061     * is 5 timeouts.
    062     ***/
    063    public static final int DEFAULT_MAX_TIMEOUTS = 5;
    064
    065    /*** The maximum number of timeouts allowed before failing. ***/
    066    private int maxTimeouts;
    067
    068    /*** The number of bytes received in the ongoing download. ***/
    069    private long totalBytesReceived = 0;
    070
    071    /*** The number of bytes sent in the ongoing upload. ***/
    072    private long totalBytesSent = 0;
    073
    074    /***
    075     * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
    076     * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
    077     * and buffered operations disabled.
    078     ***/
    079    public TFTPClient()
    080    {
    081        maxTimeouts = DEFAULT_MAX_TIMEOUTS;
    082    }
    083
    084    /***
    085     * Sets the maximum number of times a receive attempt is allowed to
    086     * timeout during a receiveFile() or sendFile() operation before ending
    087     * attempts to retry the receive and failing.
    088     * The default is DEFAULT_MAX_TIMEOUTS.
    089     *
    090     * @param numTimeouts  The maximum number of timeouts to allow.  Values
    091     *        less than 1 should not be used, but if they are, they are
    092     *        treated as 1.
    093     ***/
    094    public void setMaxTimeouts(final int numTimeouts)
    095    {
    096        if (numTimeouts < 1) {
    097            maxTimeouts = 1;
    098        } else {
    099            maxTimeouts = numTimeouts;
    100        }
    101    }
    102
    103    /***
    104     * Returns the maximum number of times a receive attempt is allowed to
    105     * timeout before ending attempts to retry the receive and failing.
    106     *
    107     * @return The maximum number of timeouts allowed.
    108     ***/
    109    public int getMaxTimeouts()
    110    {
    111        return maxTimeouts;
    112    }
    113
    114
    115    /**
    116     * @return The number of bytes received in the ongoing download
    117     */
    118    public long getTotalBytesReceived() {
    119        return totalBytesReceived;
    120    }
    121
    122    /**
    123     * @return The number of bytes sent in the ongoing download
    124     */
    125    public long getTotalBytesSent() {
    126        return totalBytesSent;
    127    }
    128
    129    /***
    130     * Requests a named file from a remote host, writes the
    131     * file to an OutputStream, closes the connection, and returns the number
    132     * of bytes read.  A local UDP socket must first be created by
    133     * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
    134     * invoking this method.  This method will not close the OutputStream
    135     * containing the file; you must close it after the method invocation.
    136     *
    137     * @param fileName  The name of the file to receive.
    138     * @param mode   The TFTP mode of the transfer (one of the MODE constants).
    139     * @param output The OutputStream to which the file should be written.
    140     * @param host   The remote host serving the file.
    141     * @param port   The port number of the remote TFTP server.
    142     * @return number of bytes read
    143     * @throws IOException If an I/O error occurs.  The nature of the
    144     *            error will be reported in the message.
    145     ***/
    146    public int receiveFile(final String fileName, final int mode, OutputStream output,
    147                           InetAddress host, final int port) throws IOException
    148    {
    149        int bytesRead = 0;
    150        int lastBlock = 0;
    151        int block = 1;
    152        int hostPort = 0;
    153        int dataLength = 0;
    154
    155        totalBytesReceived = 0;
    156
    157        if (mode == TFTP.ASCII_MODE) {
    158            output = new FromNetASCIIOutputStream(output);
    159        }
    160
    161        TFTPPacket sent = new TFTPReadRequestPacket(host, port, fileName, mode);
    162        final TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
    163
    164        beginBufferedOps();
    165
    166        boolean justStarted = true;
    167        try {
    168            do { // while more data to fetch
    169                bufferedSend(sent); // start the fetch/send an ack
    170                boolean wantReply = true;
    171                int timeouts = 0;
    172                do { // until successful response
    173                    try {
    174                        final TFTPPacket received = bufferedReceive();
    175                        // The first time we receive we get the port number and
    176                        // answering host address (for hosts with multiple IPs)
    177                        final int recdPort = received.getPort();
    178                        final InetAddress recdAddress = received.getAddress();
    179                        if (justStarted) {
    180                            justStarted = false;
    181                            if (recdPort == port) { // must not use the control port here
    182                                final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress,
    183                                        recdPort, TFTPErrorPacket.UNKNOWN_TID,
    184                                        "INCORRECT SOURCE PORT");
    185                                bufferedSend(error);
    186                                throw new IOException("Incorrect source port ("+recdPort+") in request reply.");
    187                            }
    188                            hostPort = recdPort;
    189                            ack.setPort(hostPort);
    190                            if(!host.equals(recdAddress))
    191                            {
    192                                host = recdAddress;
    193                                ack.setAddress(host);
    194                                sent.setAddress(host);
    195                            }
    196                        }
    197                        // Comply with RFC 783 indication that an error acknowledgment
    198                        // should be sent to originator if unexpected TID or host.
    199                        if (host.equals(recdAddress) && recdPort == hostPort) {
    200                            switch (received.getType()) {
    201
    202                            case TFTPPacket.ERROR:
    203                                TFTPErrorPacket error = (TFTPErrorPacket)received;
    204                                throw new IOException("Error code " + error.getError() +
    205                                                      " received: " + error.getMessage());
    206                            case TFTPPacket.DATA:
    207                                final TFTPDataPacket data = (TFTPDataPacket)received;
    208                                dataLength = data.getDataLength();
    209                                lastBlock = data.getBlockNumber();
    210
    211                                if (lastBlock == block) { // is the next block number?
    212                                    try {
    213                                        output.write(data.getData(), data.getDataOffset(), dataLength);
    214                                    } catch (final IOException e) {
    215                                        error = new TFTPErrorPacket(host, hostPort,
    216                                                                    TFTPErrorPacket.OUT_OF_SPACE,
    217                                                                    "File write failed.");
    218                                        bufferedSend(error);
    219                                        throw e;
    220                                    }
    221                                    ++block;
    222                                    if (block > 65535) {
    223                                        // wrap the block number
    224                                        block = 0;
    225                                    }
    226                                    wantReply = false; // got the next block, drop out to ack it
    227                                } else { // unexpected block number
    228                                    discardPackets();
    229                                    if (lastBlock == (block == 0 ? 65535 : block - 1)) {
    230                                        wantReply = false; // Resend last acknowledgemen
    231                                    }
    232                                }
    233                                break;
    234
    235                            default:
    236                                throw new IOException("Received unexpected packet type (" + received.getType() + ")");
    237                            }
    238                        } else { // incorrect host or TID
    239                            final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort,
    240                                    TFTPErrorPacket.UNKNOWN_TID,
    241                                    "Unexpected host or port.");
    242                            bufferedSend(error);
    243                        }
    244                    } catch (final SocketException e) {
    245                        if (++timeouts >= maxTimeouts) {
    246                            throw new IOException("Connection timed out.");
    247                        }
    248                    } catch (final InterruptedIOException e) {
    249                        if (++timeouts >= maxTimeouts) {
    250                            throw new IOException("Connection timed out.");
    251                        }
    252                    } catch (final TFTPPacketException e) {
    253                        throw new IOException("Bad packet: " + e.getMessage());
    254                    }
    255                } while(wantReply); // waiting for response
    256
    257                ack.setBlockNumber(lastBlock);
    258                sent = ack;
    259                bytesRead += dataLength;
    260                totalBytesReceived += dataLength;
    261            } while (dataLength == TFTPPacket.SEGMENT_SIZE); // not eof
    262            bufferedSend(sent); // send the final ack
    263        } finally {
    264            endBufferedOps();
    265        }
    266        return bytesRead;
    267    }
    268
    269
    270    /***
    271     * Requests a named file from a remote host, writes the
    272     * file to an OutputStream, closes the connection, and returns the number
    273     * of bytes read.  A local UDP socket must first be created by
    274     * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
    275     * invoking this method.  This method will not close the OutputStream
    276     * containing the file; you must close it after the method invocation.
    277     *
    278     * @param fileName The name of the file to receive.
    279     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    280     * @param output   The OutputStream to which the file should be written.
    281     * @param hostname The name of the remote host serving the file.
    282     * @param port     The port number of the remote TFTP server.
    283     * @return number of bytes read
    284     * @throws IOException If an I/O error occurs.  The nature of the
    285     *            error will be reported in the message.
    286     * @throws UnknownHostException  If the hostname cannot be resolved.
    287     ***/
    288    public int receiveFile(final String fileName, final int mode, final OutputStream output,
    289                           final String hostname, final int port)
    290    throws UnknownHostException, IOException
    291    {
    292        return receiveFile(fileName, mode, output, InetAddress.getByName(hostname),
    293                           port);
    294    }
    295
    296
    297    /***
    298     * Same as calling receiveFile(fileName, mode, output, host, TFTP.DEFAULT_PORT).
    299     *
    300     * @param fileName The name of the file to receive.
    301     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    302     * @param output   The OutputStream to which the file should be written.
    303     * @param host     The remote host serving the file.
    304     * @return number of bytes read
    305     * @throws IOException If an I/O error occurs.  The nature of the
    306     *            error will be reported in the message.
    307     ***/
    308    public int receiveFile(final String fileName, final int mode, final OutputStream output,
    309                           final InetAddress host)
    310    throws IOException
    311    {
    312        return receiveFile(fileName, mode, output, host, DEFAULT_PORT);
    313    }
    314
    315    /***
    316     * Same as calling receiveFile(fileName, mode, output, hostname, TFTP.DEFAULT_PORT).
    317     *
    318     * @param fileName The name of the file to receive.
    319     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    320     * @param output   The OutputStream to which the file should be written.
    321     * @param hostname The name of the remote host serving the file.
    322     * @return number of bytes read
    323     * @throws IOException If an I/O error occurs.  The nature of the
    324     *            error will be reported in the message.
    325     * @throws UnknownHostException  If the hostname cannot be resolved.
    326     ***/
    327    public int receiveFile(final String fileName, final int mode, final OutputStream output,
    328                           final String hostname)
    329    throws UnknownHostException, IOException
    330    {
    331        return receiveFile(fileName, mode, output, InetAddress.getByName(hostname),
    332                           DEFAULT_PORT);
    333    }
    334
    335
    336    /***
    337     * Requests to send a file to a remote host, reads the file from an
    338     * InputStream, sends the file to the remote host, and closes the
    339     * connection.  A local UDP socket must first be created by
    340     * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
    341     * invoking this method.  This method will not close the InputStream
    342     * containing the file; you must close it after the method invocation.
    343     *
    344     * @param fileName The name the remote server should use when creating
    345     *        the file on its file system.
    346     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    347     * @param input the input stream containing the data to be sent
    348     * @param host     The remote host receiving the file.
    349     * @param port     The port number of the remote TFTP server.
    350     * @throws IOException If an I/O error occurs.  The nature of the
    351     *            error will be reported in the message.
    352     ***/
    353    public void sendFile(final String fileName, final int mode, InputStream input,
    354                         InetAddress host, final int port) throws IOException
    355    {
    356        int block = 0;
    357        int hostPort = 0;
    358        boolean justStarted = true;
    359        boolean lastAckWait = false;
    360
    361        totalBytesSent = 0L;
    362
    363        if (mode == TFTP.ASCII_MODE) {
    364            input = new ToNetASCIIInputStream(input);
    365        }
    366
    367        TFTPPacket sent = new TFTPWriteRequestPacket(host, port, fileName, mode);
    368        final TFTPDataPacket data = new TFTPDataPacket(host, port, 0, sendBuffer, 4, 0);
    369
    370        beginBufferedOps();
    371
    372        try {
    373            do { // until eof
    374                // first time: block is 0, lastBlock is 0, send a request packet.
    375                // subsequent: block is integer starting at 1, send data packet.
    376                bufferedSend(sent);
    377                boolean wantReply = true;
    378                int timeouts = 0;
    379                do {
    380                    try {
    381                        final TFTPPacket received = bufferedReceive();
    382                        final InetAddress recdAddress = received.getAddress();
    383                        final int recdPort = received.getPort();
    384                        // The first time we receive we get the port number and
    385                        // answering host address (for hosts with multiple IPs)
    386                        if (justStarted) {
    387                            justStarted = false;
    388                            if (recdPort == port) { // must not use the control port here
    389                                final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress,
    390                                        recdPort, TFTPErrorPacket.UNKNOWN_TID,
    391                                        "INCORRECT SOURCE PORT");
    392                                bufferedSend(error);
    393                                throw new IOException("Incorrect source port ("+recdPort+") in request reply.");
    394                            }
    395                            hostPort = recdPort;
    396                            data.setPort(hostPort);
    397                            if (!host.equals(recdAddress)) {
    398                                host = recdAddress;
    399                                data.setAddress(host);
    400                                sent.setAddress(host);
    401                            }
    402                        }
    403                        // Comply with RFC 783 indication that an error acknowledgment
    404                        // should be sent to originator if unexpected TID or host.
    405                        if (host.equals(recdAddress) && recdPort == hostPort) {
    406
    407                            switch (received.getType()) {
    408                            case TFTPPacket.ERROR:
    409                                final TFTPErrorPacket error = (TFTPErrorPacket)received;
    410                                throw new IOException("Error code " + error.getError() +
    411                                                      " received: " + error.getMessage());
    412                            case TFTPPacket.ACKNOWLEDGEMENT:
    413
    414                                final int lastBlock = ((TFTPAckPacket)received).getBlockNumber();
    415
    416                                if (lastBlock == block) {
    417                                    ++block;
    418                                    if (block > 65535) {
    419                                        // wrap the block number
    420                                        block = 0;
    421                                    }
    422                                    wantReply = false; // got the ack we want
    423                                } else {
    424                                    discardPackets();
    425                                }
    426                                break;
    427                            default:
    428                                throw new IOException("Received unexpected packet type.");
    429                            }
    430                        } else { // wrong host or TID; send error
    431                            final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress,
    432                                                        recdPort,
    433                                                        TFTPErrorPacket.UNKNOWN_TID,
    434                                                        "Unexpected host or port.");
    435                            bufferedSend(error);
    436                        }
    437                    } catch (final SocketException e) {
    438                        if (++timeouts >= maxTimeouts) {
    439                            throw new IOException("Connection timed out.");
    440                        }
    441                    } catch (final InterruptedIOException e) {
    442                        if (++timeouts >= maxTimeouts) {
    443                            throw new IOException("Connection timed out.");
    444                        }
    445                    } catch (final TFTPPacketException e) {
    446                        throw new IOException("Bad packet: " + e.getMessage());
    447                    }
    448                    // retry until a good ack
    449                } while(wantReply);
    450
    451                if (lastAckWait) {
    452                    break; // we were waiting for this; now all done
    453                }
    454
    455                int dataLength = TFTPPacket.SEGMENT_SIZE;
    456                int offset = 4;
    457                int totalThisPacket = 0;
    458                int bytesRead = 0;
    459                while (dataLength > 0 &&
    460                        (bytesRead = input.read(sendBuffer, offset, dataLength)) > 0) {
    461                    offset += bytesRead;
    462                    dataLength -= bytesRead;
    463                    totalThisPacket += bytesRead;
    464                }
    465                if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) {
    466                    /* this will be our last packet -- send, wait for ack, stop */
    467                    lastAckWait = true;
    468                }
    469                data.setBlockNumber(block);
    470                data.setData(sendBuffer, 4, totalThisPacket);
    471                sent = data;
    472                totalBytesSent += totalThisPacket;
    473            } while (true); // loops until after lastAckWait is set
    474        } finally {
    475            endBufferedOps();
    476        }
    477    }
    478
    479
    480    /***
    481     * Requests to send a file to a remote host, reads the file from an
    482     * InputStream, sends the file to the remote host, and closes the
    483     * connection.  A local UDP socket must first be created by
    484     * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
    485     * invoking this method.  This method will not close the InputStream
    486     * containing the file; you must close it after the method invocation.
    487     *
    488     * @param fileName The name the remote server should use when creating
    489     *        the file on its file system.
    490     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    491     * @param input the input stream containing the data to be sent
    492     * @param hostname The name of the remote host receiving the file.
    493     * @param port     The port number of the remote TFTP server.
    494     * @throws IOException If an I/O error occurs.  The nature of the
    495     *            error will be reported in the message.
    496     * @throws UnknownHostException  If the hostname cannot be resolved.
    497     ***/
    498    public void sendFile(final String fileName, final int mode, final InputStream input,
    499                         final String hostname, final int port)
    500    throws UnknownHostException, IOException
    501    {
    502        sendFile(fileName, mode, input, InetAddress.getByName(hostname), port);
    503    }
    504
    505
    506    /***
    507     * Same as calling sendFile(fileName, mode, input, host, TFTP.DEFAULT_PORT).
    508     *
    509     * @param fileName The name the remote server should use when creating
    510     *        the file on its file system.
    511     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    512     * @param input the input stream containing the data to be sent
    513     * @param host     The name of the remote host receiving the file.
    514     * @throws IOException If an I/O error occurs.  The nature of the
    515     *            error will be reported in the message.
    516     * @throws UnknownHostException  If the hostname cannot be resolved.
    517     ***/
    518    public void sendFile(final String fileName, final int mode, final InputStream input,
    519                         final InetAddress host)
    520    throws IOException
    521    {
    522        sendFile(fileName, mode, input, host, DEFAULT_PORT);
    523    }
    524
    525    /***
    526     * Same as calling sendFile(fileName, mode, input, hostname, TFTP.DEFAULT_PORT).
    527     *
    528     * @param fileName The name the remote server should use when creating
    529     *        the file on its file system.
    530     * @param mode     The TFTP mode of the transfer (one of the MODE constants).
    531     * @param input the input stream containing the data to be sent
    532     * @param hostname The name of the remote host receiving the file.
    533     * @throws IOException If an I/O error occurs.  The nature of the
    534     *            error will be reported in the message.
    535     * @throws UnknownHostException  If the hostname cannot be resolved.
    536     ***/
    537    public void sendFile(final String fileName, final int mode, final InputStream input,
    538                         final String hostname)
    539    throws UnknownHostException, IOException
    540    {
    541        sendFile(fileName, mode, input, InetAddress.getByName(hostname),
    542                 DEFAULT_PORT);
    543    }
    544}