TFTPClient.java

  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. package org.apache.commons.net.tftp;

  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.  * The TFTPClient class encapsulates all the aspects of the TFTP protocol necessary to receive and send files through TFTP. It is derived from the
  29.  * {@link org.apache.commons.net.tftp.TFTP} because it is more convenient than using aggregation, and as a result exposes the same set of methods to allow you
  30.  * to deal with the TFTP protocol directly. However, almost every user should only be concerend with the the
  31.  * {@link org.apache.commons.net.DatagramSocketClient#open open() }, {@link org.apache.commons.net.DatagramSocketClient#close close() }, {@link #sendFile
  32.  * sendFile() }, and {@link #receiveFile receiveFile() } methods. Additionally, the {@link #setMaxTimeouts setMaxTimeouts() } and
  33.  * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } methods may be of importance for performance tuning.
  34.  * <p>
  35.  * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to
  36.  * worry about the internals.
  37.  *
  38.  *
  39.  * @see TFTP
  40.  * @see TFTPPacket
  41.  * @see TFTPPacketException
  42.  */

  43. public class TFTPClient extends TFTP {
  44.     /**
  45.      * The default number of times a {@code receive} attempt is allowed to timeout before ending attempts to retry the {@code receive} and failing.
  46.      * The default is 5 timeouts.
  47.      */
  48.     public static final int DEFAULT_MAX_TIMEOUTS = 5;

  49.     /** The maximum number of timeouts allowed before failing. */
  50.     private int maxTimeouts;

  51.     /** The number of bytes received in the ongoing download. */
  52.     private long totalBytesReceived;

  53.     /** The number of bytes sent in the ongoing upload. */
  54.     private long totalBytesSent;

  55.     /**
  56.      * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, and buffered
  57.      * operations disabled.
  58.      */
  59.     public TFTPClient() {
  60.         maxTimeouts = DEFAULT_MAX_TIMEOUTS;
  61.     }

  62.     /**
  63.      * Returns the maximum number of times a {@code receive} attempt is allowed to timeout before ending attempts to retry the {@code receive} and failing.
  64.      *
  65.      * @return The maximum number of timeouts allowed.
  66.      */
  67.     public int getMaxTimeouts() {
  68.         return maxTimeouts;
  69.     }

  70.     /**
  71.      * @return The number of bytes received in the ongoing download
  72.      */
  73.     public long getTotalBytesReceived() {
  74.         return totalBytesReceived;
  75.     }

  76.     /**
  77.      * @return The number of bytes sent in the ongoing download
  78.      */
  79.     public long getTotalBytesSent() {
  80.         return totalBytesSent;
  81.     }

  82.     /**
  83.      * Same as calling receiveFile(fileName, mode, output, host, TFTP.DEFAULT_PORT).
  84.      *
  85.      * @param fileName The name of the file to receive.
  86.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  87.      * @param output   The OutputStream to which the file should be written.
  88.      * @param host     The remote host serving the file.
  89.      * @return number of bytes read
  90.      * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message.
  91.      */
  92.     public int receiveFile(final String fileName, final int mode, final OutputStream output, final InetAddress host) throws IOException {
  93.         return receiveFile(fileName, mode, output, host, DEFAULT_PORT);
  94.     }

  95.     /**
  96.      * Requests a named file from a remote host, writes the file to an OutputStream, closes the connection, and returns the number of bytes read. A local UDP
  97.      * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close
  98.      * the OutputStream containing the file; you must close it after the method invocation.
  99.      *
  100.      * @param fileName The name of the file to receive.
  101.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  102.      * @param output   The OutputStream to which the file should be written.
  103.      * @param host     The remote host serving the file.
  104.      * @param port     The port number of the remote TFTP server.
  105.      * @return number of bytes read
  106.      * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message.
  107.      */
  108.     public int receiveFile(final String fileName, final int mode, OutputStream output, InetAddress host, final int port) throws IOException {
  109.         int bytesRead = 0;
  110.         int lastBlock = 0;
  111.         int block = 1;
  112.         int hostPort = 0;
  113.         int dataLength = 0;

  114.         totalBytesReceived = 0;

  115.         if (mode == TFTP.ASCII_MODE) {
  116.             output = new FromNetASCIIOutputStream(output);
  117.         }

  118.         TFTPPacket sent = new TFTPReadRequestPacket(host, port, fileName, mode);
  119.         final TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);

  120.         beginBufferedOps();

  121.         boolean justStarted = true;
  122.         try {
  123.             do { // while more data to fetch
  124.                 bufferedSend(sent); // start the fetch/send an ack
  125.                 boolean wantReply = true;
  126.                 int timeouts = 0;
  127.                 do { // until successful response
  128.                     try {
  129.                         final TFTPPacket received = bufferedReceive();
  130.                         // The first time we receive we get the port number and
  131.                         // answering host address (for hosts with multiple IPs)
  132.                         final int recdPort = received.getPort();
  133.                         final InetAddress recdAddress = received.getAddress();
  134.                         if (justStarted) {
  135.                             justStarted = false;
  136.                             if (recdPort == port) { // must not use the control port here
  137.                                 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT");
  138.                                 bufferedSend(error);
  139.                                 throw new IOException("Incorrect source port (" + recdPort + ") in request reply.");
  140.                             }
  141.                             hostPort = recdPort;
  142.                             ack.setPort(hostPort);
  143.                             if (!host.equals(recdAddress)) {
  144.                                 host = recdAddress;
  145.                                 ack.setAddress(host);
  146.                                 sent.setAddress(host);
  147.                             }
  148.                         }
  149.                         // Comply with RFC 783 indication that an error acknowledgment
  150.                         // should be sent to originator if unexpected TID or host.
  151.                         if (host.equals(recdAddress) && recdPort == hostPort) {
  152.                             switch (received.getType()) {

  153.                             case TFTPPacket.ERROR:
  154.                                 TFTPErrorPacket error = (TFTPErrorPacket) received;
  155.                                 throw new IOException("Error code " + error.getError() + " received: " + error.getMessage());
  156.                             case TFTPPacket.DATA:
  157.                                 final TFTPDataPacket data = (TFTPDataPacket) received;
  158.                                 dataLength = data.getDataLength();
  159.                                 lastBlock = data.getBlockNumber();

  160.                                 if (lastBlock == block) { // is the next block number?
  161.                                     try {
  162.                                         output.write(data.getData(), data.getDataOffset(), dataLength);
  163.                                     } catch (final IOException e) {
  164.                                         error = new TFTPErrorPacket(host, hostPort, TFTPErrorPacket.OUT_OF_SPACE, "File write failed.");
  165.                                         bufferedSend(error);
  166.                                         throw e;
  167.                                     }
  168.                                     ++block;
  169.                                     if (block > 65535) {
  170.                                         // wrap the block number
  171.                                         block = 0;
  172.                                     }
  173.                                     wantReply = false; // got the next block, drop out to ack it
  174.                                 } else { // unexpected block number
  175.                                     discardPackets();
  176.                                     if (lastBlock == (block == 0 ? 65535 : block - 1)) {
  177.                                         wantReply = false; // Resend last acknowledgemen
  178.                                     }
  179.                                 }
  180.                                 break;

  181.                             default:
  182.                                 throw new IOException("Received unexpected packet type (" + received.getType() + ")");
  183.                             }
  184.                         } else { // incorrect host or TID
  185.                             final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port.");
  186.                             bufferedSend(error);
  187.                         }
  188.                     } catch (final SocketException | InterruptedIOException e) {
  189.                         if (++timeouts >= maxTimeouts) {
  190.                             throw new IOException("Connection timed out.");
  191.                         }
  192.                     } catch (final TFTPPacketException e) {
  193.                         throw new IOException("Bad packet: " + e.getMessage());
  194.                     }
  195.                 } while (wantReply); // waiting for response

  196.                 ack.setBlockNumber(lastBlock);
  197.                 sent = ack;
  198.                 bytesRead += dataLength;
  199.                 totalBytesReceived += dataLength;
  200.             } while (dataLength == TFTPPacket.SEGMENT_SIZE); // not eof
  201.             bufferedSend(sent); // send the final ack
  202.         } finally {
  203.             endBufferedOps();
  204.         }
  205.         return bytesRead;
  206.     }

  207.     /**
  208.      * Same as calling receiveFile(fileName, mode, output, hostname, TFTP.DEFAULT_PORT).
  209.      *
  210.      * @param fileName The name of the file to receive.
  211.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  212.      * @param output   The OutputStream to which the file should be written.
  213.      * @param hostname The name of the remote host serving the file.
  214.      * @return number of bytes read
  215.      * @throws IOException          If an I/O error occurs. The nature of the error will be reported in the message.
  216.      * @throws UnknownHostException If the hostname cannot be resolved.
  217.      */
  218.     public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname) throws UnknownHostException, IOException {
  219.         return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), DEFAULT_PORT);
  220.     }

  221.     /**
  222.      * Requests a named file from a remote host, writes the file to an OutputStream, closes the connection, and returns the number of bytes read. A local UDP
  223.      * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close
  224.      * the OutputStream containing the file; you must close it after the method invocation.
  225.      *
  226.      * @param fileName The name of the file to receive.
  227.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  228.      * @param output   The OutputStream to which the file should be written.
  229.      * @param hostname The name of the remote host serving the file.
  230.      * @param port     The port number of the remote TFTP server.
  231.      * @return number of bytes read
  232.      * @throws IOException          If an I/O error occurs. The nature of the error will be reported in the message.
  233.      * @throws UnknownHostException If the hostname cannot be resolved.
  234.      */
  235.     public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname, final int port)
  236.             throws UnknownHostException, IOException {
  237.         return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), port);
  238.     }

  239.     /**
  240.      * Same as calling sendFile(fileName, mode, input, host, TFTP.DEFAULT_PORT).
  241.      *
  242.      * @param fileName The name the remote server should use when creating the file on its file system.
  243.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  244.      * @param input    the input stream containing the data to be sent
  245.      * @param host     The name of the remote host receiving the file.
  246.      * @throws IOException          If an I/O error occurs. The nature of the error will be reported in the message.
  247.      * @throws UnknownHostException If the hostname cannot be resolved.
  248.      */
  249.     public void sendFile(final String fileName, final int mode, final InputStream input, final InetAddress host) throws IOException {
  250.         sendFile(fileName, mode, input, host, DEFAULT_PORT);
  251.     }

  252.     /**
  253.      * Requests to send a file to a remote host, reads the file from an InputStream, sends the file to the remote host, and closes the connection. A local UDP
  254.      * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close
  255.      * the InputStream containing the file; you must close it after the method invocation.
  256.      *
  257.      * @param fileName The name the remote server should use when creating the file on its file system.
  258.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  259.      * @param input    the input stream containing the data to be sent
  260.      * @param host     The remote host receiving the file.
  261.      * @param port     The port number of the remote TFTP server.
  262.      * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message.
  263.      */
  264.     public void sendFile(final String fileName, final int mode, InputStream input, InetAddress host, final int port) throws IOException {
  265.         int block = 0;
  266.         int hostPort = 0;
  267.         boolean justStarted = true;
  268.         boolean lastAckWait = false;

  269.         totalBytesSent = 0L;

  270.         if (mode == TFTP.ASCII_MODE) {
  271.             input = new ToNetASCIIInputStream(input);
  272.         }

  273.         TFTPPacket sent = new TFTPWriteRequestPacket(host, port, fileName, mode);
  274.         final TFTPDataPacket data = new TFTPDataPacket(host, port, 0, sendBuffer, 4, 0);

  275.         beginBufferedOps();

  276.         try {
  277.             do { // until eof
  278.                  // first time: block is 0, lastBlock is 0, send a request packet.
  279.                  // subsequent: block is integer starting at 1, send data packet.
  280.                 bufferedSend(sent);
  281.                 boolean wantReply = true;
  282.                 int timeouts = 0;
  283.                 do {
  284.                     try {
  285.                         final TFTPPacket received = bufferedReceive();
  286.                         final InetAddress recdAddress = received.getAddress();
  287.                         final int recdPort = received.getPort();
  288.                         // The first time we receive we get the port number and
  289.                         // answering host address (for hosts with multiple IPs)
  290.                         if (justStarted) {
  291.                             justStarted = false;
  292.                             if (recdPort == port) { // must not use the control port here
  293.                                 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT");
  294.                                 bufferedSend(error);
  295.                                 throw new IOException("Incorrect source port (" + recdPort + ") in request reply.");
  296.                             }
  297.                             hostPort = recdPort;
  298.                             data.setPort(hostPort);
  299.                             if (!host.equals(recdAddress)) {
  300.                                 host = recdAddress;
  301.                                 data.setAddress(host);
  302.                                 sent.setAddress(host);
  303.                             }
  304.                         }
  305.                         // Comply with RFC 783 indication that an error acknowledgment
  306.                         // should be sent to originator if unexpected TID or host.
  307.                         if (host.equals(recdAddress) && recdPort == hostPort) {

  308.                             switch (received.getType()) {
  309.                             case TFTPPacket.ERROR:
  310.                                 final TFTPErrorPacket error = (TFTPErrorPacket) received;
  311.                                 throw new IOException("Error code " + error.getError() + " received: " + error.getMessage());
  312.                             case TFTPPacket.ACKNOWLEDGEMENT:

  313.                                 final int lastBlock = ((TFTPAckPacket) received).getBlockNumber();

  314.                                 if (lastBlock == block) {
  315.                                     ++block;
  316.                                     if (block > 65535) {
  317.                                         // wrap the block number
  318.                                         block = 0;
  319.                                     }
  320.                                     wantReply = false; // got the ack we want
  321.                                 } else {
  322.                                     discardPackets();
  323.                                 }
  324.                                 break;
  325.                             default:
  326.                                 throw new IOException("Received unexpected packet type.");
  327.                             }
  328.                         } else { // wrong host or TID; send error
  329.                             final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port.");
  330.                             bufferedSend(error);
  331.                         }
  332.                     } catch (final SocketException | InterruptedIOException e) {
  333.                         if (++timeouts >= maxTimeouts) {
  334.                             throw new IOException("Connection timed out.");
  335.                         }
  336.                     } catch (final TFTPPacketException e) {
  337.                         throw new IOException("Bad packet: " + e.getMessage());
  338.                     }
  339.                     // retry until a good ack
  340.                 } while (wantReply);

  341.                 if (lastAckWait) {
  342.                     break; // we were waiting for this; now all done
  343.                 }

  344.                 int dataLength = TFTPPacket.SEGMENT_SIZE;
  345.                 int offset = 4;
  346.                 int totalThisPacket = 0;
  347.                 int bytesRead = 0;
  348.                 while (dataLength > 0 && (bytesRead = input.read(sendBuffer, offset, dataLength)) > 0) {
  349.                     offset += bytesRead;
  350.                     dataLength -= bytesRead;
  351.                     totalThisPacket += bytesRead;
  352.                 }
  353.                 if (totalThisPacket < TFTPPacket.SEGMENT_SIZE) {
  354.                     /* this will be our last packet -- send, wait for ack, stop */
  355.                     lastAckWait = true;
  356.                 }
  357.                 data.setBlockNumber(block);
  358.                 data.setData(sendBuffer, 4, totalThisPacket);
  359.                 sent = data;
  360.                 totalBytesSent += totalThisPacket;
  361.             } while (true); // loops until after lastAckWait is set
  362.         } finally {
  363.             endBufferedOps();
  364.         }
  365.     }

  366.     /**
  367.      * Same as calling sendFile(fileName, mode, input, hostname, TFTP.DEFAULT_PORT).
  368.      *
  369.      * @param fileName The name the remote server should use when creating the file on its file system.
  370.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  371.      * @param input    the input stream containing the data to be sent
  372.      * @param hostname The name of the remote host receiving the file.
  373.      * @throws IOException          If an I/O error occurs. The nature of the error will be reported in the message.
  374.      * @throws UnknownHostException If the hostname cannot be resolved.
  375.      */
  376.     public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname) throws UnknownHostException, IOException {
  377.         sendFile(fileName, mode, input, InetAddress.getByName(hostname), DEFAULT_PORT);
  378.     }

  379.     /**
  380.      * Requests to send a file to a remote host, reads the file from an InputStream, sends the file to the remote host, and closes the connection. A local UDP
  381.      * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close
  382.      * the InputStream containing the file; you must close it after the method invocation.
  383.      *
  384.      * @param fileName The name the remote server should use when creating the file on its file system.
  385.      * @param mode     The TFTP mode of the transfer (one of the MODE constants).
  386.      * @param input    the input stream containing the data to be sent
  387.      * @param hostname The name of the remote host receiving the file.
  388.      * @param port     The port number of the remote TFTP server.
  389.      * @throws IOException          If an I/O error occurs. The nature of the error will be reported in the message.
  390.      * @throws UnknownHostException If the hostname cannot be resolved.
  391.      */
  392.     public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname, final int port)
  393.             throws UnknownHostException, IOException {
  394.         sendFile(fileName, mode, input, InetAddress.getByName(hostname), port);
  395.     }

  396.     /**
  397.      * Sets the maximum number of times a {@code receive} attempt is allowed to timeout during a receiveFile() or sendFile() operation before ending
  398.      * attempts to retry the {@code receive} and failing. The default is DEFAULT_MAX_TIMEOUTS.
  399.      *
  400.      * @param numTimeouts The maximum number of timeouts to allow. Values less than 1 should not be used, but if they are, they are treated as 1.
  401.      */
  402.     public void setMaxTimeouts(final int numTimeouts) {
  403.         maxTimeouts = Math.max(numTimeouts, 1);
  404.     }
  405. }