/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Geoffrey Said. * Portions created by Geoffrey Said are Copyright (C) 2006. * All Rights Reserved. * * Contributor(s): Geoffrey Said */ package com.slamd.tftp; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.Arrays; import com.slamd.stat.IncrementalTracker; import com.slamd.stat.IntegerValueTracker; import com.slamd.stat.StatTracker; import com.slamd.stat.TimeTracker; /** * Defines a client that can connect with a TFTP server and retreive files. * Provides features including the ability to set custom timeouts and number of * retries as well as the download method. Only error messages are stored. No * data is kept (only the total ammount of bytes is stored). * * @author 2X Geoffrey Said */ public class TFTPClient { // Static public variables declaration. /** * Standard TFTP port to use when intializing communication with the * TFTP server. */ public static final int DEFAULT_TFTP_SERVER_PORT = 69; /** * Data terminator to include in TFTP packet. */ public static final byte PACKET_DATA_TERMINATOR = 00; /** * Maximum TFTP packet size to be used in creating byte buffers. */ public static final int MAX_BUFFER_SIZE = 516; /** * Default client UDP port to use if no random one is used. */ public static final int DEFAULT_UDP_PORT = 6254; /** * Default timeout, in milliseconds, to wait before the client retransmits * last packet. */ public static final int DEFAULT_SOCKET_TIMEOUT = 3000; /** * Default number of retries before transmitting an error TFTP packet to * the server. */ public static final int DEFAULT_NUMBER_OF_RETRIES = 3; /** * Default download mode when building a data TFTP packet. */ public static final String DEFAULT_MODE = "octet"; /** * Default TFTP server IP. */ public static final String DEFAULT_TFTP_SERVER_IP = "127.0.0.1"; /** * Default filename to fetch from the TFTP server. */ public static final String DEFAULT_FILENAME = "filename"; /** * Value that defines a read request TFTP packet. */ public static final byte PACKET_RRQ = 01; /** * Value that defines a write request TFTP packet. */ public static final byte PACKET_WRQ = 02; /** * Value that defines a data TFTP packet. */ public static final byte PACKET_DATA = 03; /** * Value that defines an acknowledge TFTP packet. */ public static final byte PACKET_ACK = 04; /** * Value that defines an error TFTP packet. */ public static final byte PACKET_ERROR = 05; /** * Ascii mode description string used in building a data TFTP packet */ public static final String DOWNLOAD_NETASCII = "netascii"; /** * Binary mode description string used in building a data TFTP packet */ public static final String DOWNLOAD_OCTET = "octet"; /** * Mail mode description string used in building a data TFTP packet. */ public static final String DOWNLOAD_MAIL = "mail"; /** * Value that defines a <B>NOT DEFINED</B> error TFTP packet. */ public static final byte ERROR_NOT_DEFINED = 00; /** * Value that defines a <B>FILE NOT FOUND</B> error TFTP packet. */ public static final byte ERROR_FILE_NOT_FOUND = 01; /** * Value that defines an <B>ACCESS VIOLATION</B> error TFTP packet. */ public static final byte ERROR_ACCESS_VIOLATION = 02; /** * Value that defines a <B>DISK FULL</B> error TFTP packet. */ public static final byte ERROR_DISK_FULL = 03; /** * Value that defines an <B>ILLEGAL TFTP OPERATION</B> error TFTP packet. */ public static final byte ERROR_ILLEGAL_TFTP_OPERATION = 04; /** * Value that defines an <B>UNKNOWN TRANSFER ID</B> error TFTP packet. */ public static final byte ERROR_UNKNOWN_TRANSFER_ID = 05; /** * Value that defines a <B>FILE ALREADY EXISTS</B> error TFTP packet. */ public static final byte ERROR_FILE_ALREADY_EXISTS = 06; /** * Value that defines a <B>NO SUCH USER</B> error TFTP packet. */ public static final byte ERROR_NO_SUCH_USER = 07; /** * String array that defines all error messages to write in an error TFTP * packet. */ public static final String[] ERROR_MESSAGES = { "Timeout error occurred", "File not found", "Access violation", "Disk is full", "Illegal tftp operation", "Unknown transfer ID", "File already exists", "No such user", "No error message" }; /** * The display name of the stat tracker that stores the ammount of time to * service a file download request. */ public static final String STAT_TRACKER_TFTP_DOWNLOAD_TIME = "TFTP Client Download Time"; /** * The display name of the stat tracker that stores the ammount of file * download requests. */ public static final String STAT_TRACKER_TFTP_FILE_DOWNLOADS = "TFTP Client File Downloads"; /** * The display name of the stat tracker that stores the number of TFTP * packet retransmittions. This happens when UDP packets get lost or the * server is not responding. */ public static final String STAT_TRACKER_TFTP_RETRIES = "TFTP Packet Retransmittions"; /** * The display name of the stat tracker that stores the ammount of bytes * downloaded by the TFTP client. */ public static final String STAT_TRACKER_TFTP_DOWNLOADED_BYTES = "TFTP Data Bytes Downloaded"; /** * The display name of the stat tracker that stores the actual ammount of * bytes sent by the TFTP server to service the request. */ public static final String STAT_TRACKER_TFTP_ACTUAL_DOWNLOADED_BYTES = "TFTP Actual Data Bytes Downloaded"; /** * The display name of the stat tracker that stores the total number of * failed downloads. */ public static final String STAT_TRACKER_TFTP_FAILED_DOWNLOADS = "TFTP Client Failed Downloads"; // Private instance variables. // Byte value that defines a no error situation. private static final byte ERROR_NO_ERROR = 10; // UDP socket and packet objects. private DatagramSocket socket; private DatagramPacket sendPacket, receivePacket; // Byte array stream objects. Used to store all data sent/received. private ByteArrayOutputStream receivedData = new ByteArrayOutputStream(); private ByteArrayOutputStream tftpSendPacket = new ByteArrayOutputStream(); private ByteArrayInputStream tftpReceivedPacket; // Object instance status variables. // Byte arrays private byte[] opCode = new byte[2]; private byte[] errorCode = new byte[2]; private byte[] blockNumber = new byte[2]; private byte[] data; // Integers that store communication ports, the length of the last piece of // data received, and the total data received private int localPort; private int remotePort; private int dataLength; private int totalDataLength; private int totalActualDataLength; // Integers that store UDP socket timeout and number of retries // before client sends error packet. private int timeout; private int retries; // Strings storing fileName to retrieve, data transfer mode, error messages, // and server IP address. private String fileName; private String mode; private String errorMessage; private String serverIP; // Flags that store the status of statistics collection private boolean collectStatistics; private boolean collectingNow; // Statistics Trackers that are maintained by this TFTP client private TimeTracker tftpDownloadTime; private IncrementalTracker tftpFileDownloads; private IncrementalTracker tftpRetries; private IncrementalTracker tftpFailedDownloads; private IntegerValueTracker tftpDownloadBytes; private IntegerValueTracker tftpActualDownloadedBytes; // End of variable declarations. /** * Class constructor which sets every instance variable to its default value. * <P> * In particular: * <UL> * <LI>Server IP is set to <I>127.0.0.1</I>. * <LI>File name is set to the file <I>filename</I>. * </UL> */ public TFTPClient() { setVariables(DEFAULT_TFTP_SERVER_IP, DEFAULT_FILENAME, DEFAULT_TFTP_SERVER_PORT, PACKET_RRQ, DEFAULT_MODE, DEFAULT_SOCKET_TIMEOUT, DEFAULT_NUMBER_OF_RETRIES); } /** * Class constructur that initialises the TFTP server IP to the * <CODE>serverIP</CODE> variable. All other variables are set to default. * <P> * In particular: * <UL> * <LI>File name is set to the file <I>filename</I>. * </UL> * * @param serverIP the TFTP server IP address to connect to when retrieving * files. */ public TFTPClient(String serverIP) { setVariables(serverIP, DEFAULT_FILENAME, DEFAULT_TFTP_SERVER_PORT, PACKET_RRQ, DEFAULT_MODE, DEFAULT_SOCKET_TIMEOUT, DEFAULT_NUMBER_OF_RETRIES); } /** * Class constructur that initialises the TFTP server IP to the * <CODE>serverIP</CODE> variable and the file name to the string stored in * the <CODE>filename</CODE> variable. All other instance variables are set * to their defaults. * * @param serverIP the TFTP server IP address to connect to when retrieving * files. * @param filename the name of the file to fetch from the TFTP server. */ public TFTPClient(String serverIP, String filename) { setVariables(serverIP, filename, DEFAULT_TFTP_SERVER_PORT, PACKET_RRQ, DEFAULT_MODE, DEFAULT_SOCKET_TIMEOUT, DEFAULT_NUMBER_OF_RETRIES); } /** * Class constructor that initialises the TFTP server IP to the * <CODE>serverIP</CODE> variable and the packet time out to the * <CODE>timeout</CODE> variable. All other instance variables are set to * their defaults. * <P> * In particular: * <UL> * <LI> File name is set to the file <I>filename</I>. * </UL> * * @param serverIP the TFTP server IP address to connect to when retrieving * files. * @param timeout the time out, in milliseconds, before a packet is * re-tansmitted. */ public TFTPClient(String serverIP, int timeout) { setVariables(serverIP, DEFAULT_FILENAME, DEFAULT_TFTP_SERVER_PORT, PACKET_RRQ, DEFAULT_MODE, timeout, DEFAULT_NUMBER_OF_RETRIES); } /** * Class constructor that intialises the TFTP server IP to the * <CODE>serverIP</CODE> variable, the packet time out to the * <CODE>timeout</CODE>, and the maximum number of packet retransmitions to * <CODE>retries</CODE>. * <P> * In particular: * <UL> * <LI> File name is set to the file <I>filename</I>. * </UL> * * @param serverIP the TFTP server IP address to connect to when retrieving * files. * @param timeout the time out, in milliseconds, before a packet is * re-tansmitted. * @param retries the maximum number of packet retransmitions before * sending an error TFTP packet. */ public TFTPClient(String serverIP, int timeout, int retries) { setVariables(serverIP, DEFAULT_FILENAME, DEFAULT_TFTP_SERVER_PORT, PACKET_RRQ, DEFAULT_MODE, timeout, retries); } /* * The setVariables method calls other set methods to correctly build an * instance. Used by the constructors to initialise TFTPClient objects */ private void setVariables(String serverIP, String filename, int remotePort, byte opCode, String mode, int timeout, int retries) { byte[] blockNumber = {0,0}; setServerIP(serverIP); setRemotePort(remotePort); setFileName(filename); setOpCode(opCode); setDataTransferMode(mode); setErrorCode(ERROR_NO_ERROR); setBlockNumber(blockNumber); setTimeout(timeout); setNumberOfRetries(retries); setDataLength(0); setTotalDataLength(0); setLocalPort(DEFAULT_UDP_PORT); setErrorMessage(ERROR_MESSAGES[8]); disableStatisticsCollection(); } // Public set methods. /** * Assigns the passed filename to the object's <CODE>fileName</CODE> field. * * @param fileName the file name to retreive from the TFTP server. */ public void setFileName(String fileName) { this.fileName = fileName; } /** * Assigns the download mode according to the passed parameter. This affects * the <CODE>mode</CODE> field. Contains checks to avoid illegal options. * If an illegal mode value is passed, the value in <CODE>DEFAULT_MODE</CODE> * is used. * * @param mode a string containing the download mode. */ public void setDataTransferMode(String mode) { boolean defaultValue = false; // Checks to detect correct values if (! mode.equalsIgnoreCase(DOWNLOAD_NETASCII)) if (! mode.equalsIgnoreCase(DOWNLOAD_OCTET)) if (! mode.equalsIgnoreCase(DOWNLOAD_MAIL)) defaultValue = true; // Accept the passed value according to the boolean variable defaultValue if (defaultValue) this.mode = DEFAULT_MODE; else this.mode = mode; } /** * Assigns passed server IP to the <CODE>serverIP</CODE> field. * No checks are performed. * * @param serverIP the server IP address to use. */ public void setServerIP(String serverIP) { this.serverIP = serverIP; } /** * Assigns passed timeout, in milliseconds, to the <CODE>timeout</CODE> field. * If the value is negative then the number 0 is assigned. This disables * socket timeout checking. * * @param timeout an integer storing the timeout in milliseconds. */ public void setTimeout(int timeout) { if (timeout >= 0) this.timeout = timeout; else this.timeout = 0; } /** * Assigns the passed number of retries to the <CODE>retries</CODE> field. * If the value is negative then the number 0 is assigned. This will force * the client to send an error TFTP packet and close if an * acknowledgement/data packet is not received. * * @param retries the maximum number of retransmissions that the client is * allowed to perform before sending an error packet. */ public void setNumberOfRetries(int retries) { if (retries >= 0) this.retries = retries; else this.retries = 0; } /** * Enables the collection of statistical data by setting the * <CODE>collectStatistics</CODE> and <CODE>collectingNow</CODE> variables * to true. Initializes all statistics trackers and calls the * <CODE>startTracker</CODE> method for every tracker. * * @param clientID the Client ID to use for the trackers. * @param threadID the Thread ID to use of the trackers. * @param collectionInterval the collection interval to use for * the trackers. */ public void enableStatisticsCollection(String clientID, String threadID, int collectionInterval) { // If no statistics have been enabled, enabled them collectStatistics = true; // Initialise all trackers tftpDownloadTime = new TimeTracker(clientID, threadID, STAT_TRACKER_TFTP_DOWNLOAD_TIME, collectionInterval); tftpDownloadBytes = new IntegerValueTracker(clientID, threadID, STAT_TRACKER_TFTP_DOWNLOADED_BYTES, collectionInterval); tftpActualDownloadedBytes = new IntegerValueTracker(clientID, threadID, STAT_TRACKER_TFTP_ACTUAL_DOWNLOADED_BYTES, collectionInterval); tftpFileDownloads = new IncrementalTracker(clientID, threadID, STAT_TRACKER_TFTP_FILE_DOWNLOADS, collectionInterval); tftpFailedDownloads = new IncrementalTracker(clientID, threadID, STAT_TRACKER_TFTP_FAILED_DOWNLOADS, collectionInterval); tftpRetries = new IncrementalTracker(clientID, threadID, STAT_TRACKER_TFTP_RETRIES, collectionInterval); // Start all trackers tftpDownloadTime.startTracker(); tftpDownloadBytes.startTracker(); tftpActualDownloadedBytes.startTracker(); tftpFileDownloads.startTracker(); tftpFailedDownloads.startTracker(); tftpRetries.startTracker(); // We are now ready to collect any statistics data collectingNow = true; } /** * Sets the variables <CODE>collectStatistics</CODE> and * <CODE>collectingNow</CODE> to false. This disables the collection of * statistical data. */ public void disableStatisticsCollection() { collectStatistics = false; collectingNow = false; } // Private set methods. /* * Assigns the local port provided by the socket object. If passed value is * 0 or negative, the value of DEFAULT_UDP_PORT is used. */ private void setLocalPort(int localPort) { if (localPort > 0) this.localPort = localPort; else this.localPort = DEFAULT_UDP_PORT; } /* * Assigns the remote port which is contained in the first packet sent by the * TFTP server. */ private void setRemotePort(int remotePort) { if (remotePort > 0) this.remotePort = remotePort; else this.remotePort = DEFAULT_TFTP_SERVER_PORT; } /* * Assigns the number of bytes received to the dataLength field. 0 is used * if a negative value is passed. */ private void setDataLength(int dataLength) { if (dataLength > 0) this.dataLength = dataLength; else this.dataLength = 0; } /* * Assigns the total number of bytes received to the totalDataLength field. * If a negative number is passed, 0 is stored. */ private void setTotalDataLength(int totalDataLength) { if (totalDataLength > 0) this.totalDataLength = totalDataLength; else this.totalDataLength = 0; } /* * Assigns the passed byte value to the opCode byte array's second location. * If an illegal value is passed, the PACKET_RRQ value is set. */ private void setOpCode(byte secondOpCodeByte) { this.opCode[0] = 0; switch (secondOpCodeByte) { case PACKET_RRQ: case PACKET_WRQ: case PACKET_DATA: case PACKET_ACK: case PACKET_ERROR: this.opCode[1] = secondOpCodeByte; break; default: this.opCode[1] = PACKET_RRQ; } } /* * Assigns the passed error code value to the errorCode byte array's second * location. If an illegal value is passed, the ERROR_NO_ERROR value is set. */ private void setErrorCode(byte secondErrorCodeByte) { this.errorCode[0] = 0; switch (secondErrorCodeByte) { case ERROR_NO_ERROR: case ERROR_NOT_DEFINED: case ERROR_FILE_NOT_FOUND: case ERROR_ACCESS_VIOLATION: case ERROR_DISK_FULL: case ERROR_ILLEGAL_TFTP_OPERATION: case ERROR_UNKNOWN_TRANSFER_ID: case ERROR_FILE_ALREADY_EXISTS: case ERROR_NO_SUCH_USER: this.errorCode[1] = secondErrorCodeByte; break; default: this.errorCode[1] = ERROR_NO_ERROR; } } /* * Initialises the blockNumber byte array. */ private void setBlockNumber(byte[] blockNumber) { this.blockNumber = blockNumber; } /* * Assigns the passed message to the errorMessage String field. */ private void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } /* * Assigns the integer to the totalActualDataLength field. */ private void setTotalActualDataLength(int actualLength) { if (actualLength >= 0) this.totalActualDataLength = actualLength; else this.totalActualDataLength = 0; } // End of set methods. // Public get methods. /** * Returns the file name stored in the <CODE>fileName</CODE> field. * * @return the file name to fetch from the TFTP server. */ public String getFileName() { return fileName; } /** * Returns the data download mode stored in the <CODE>mode</CODE> field. * * @return the transfer mode to used during file transfers. */ public String getDataTransferMode() { return mode; } /** * Returns the server IP address stored in the <CODE>serverIP</CODE> field. * * @return the TFTP server IP address. */ public String getServerIP() { return serverIP; } /** * Returns the error message stored in the <CODE>errorMessage</CODE> field. * * @return the error message. */ public String getErrorMessage() { return errorMessage; } /** * Returns the local socket port stored in the <CODE>localPort</CODE> field. * * @return the local port number. */ public int getLocalPort() { return localPort; } /** * Returns the remote socket port stored in the <CODE>remotePort</CODE> field. * * @return the remote port number. */ public int getRemotePort() { return remotePort; } /** * Returns the socket time out stored in the <CODE>timeout</CODE> field. * * @return the time out. */ public int getTimeout() { return timeout; } /** * Returns the number of retries stored in the <CODE>retries</CODE> field. * * @return the maximum number of retransmissions. */ public int getNumberOfRetries() { return retries; } /** * Returns the <CODE>errorCode</CODE> byte array. * * @return the error code byte array. */ public byte[] getErrorCode() { return errorCode; } /** * Returns the value stored in the <CODE>totalDataLength</CODE>. * * @return the size of the fetched file as an integer. */ public int getFetchedDataLength() { return totalDataLength; } /** * Returns whether statistics have been enabled. * * @return true if statistics have been enabled, * false if not. */ public boolean isStatisticsEnabled() { return collectStatistics; } /** * Returns whether object is currently collecting statistics. * * @return true if object is currently collecting statistics, * false if it is not. */ public boolean areWeCollectingNow() { return collectingNow; } /** * If statistics are enabled, the trackers are stopped and returned as * a <CODE>StatTracker</CODE> array. * * @return all statistics trackers in a <CODE>StatTracker</CODE> array */ public StatTracker[] getStatTrackers() { if (isStatisticsEnabled()) { if (areWeCollectingNow()) { tftpDownloadTime.stopTracker(); tftpDownloadBytes.stopTracker(); tftpActualDownloadedBytes.stopTracker(); tftpFileDownloads.stopTracker(); tftpFailedDownloads.stopTracker(); tftpRetries.stopTracker(); collectingNow = false; } return new StatTracker[] { tftpDownloadTime, tftpActualDownloadedBytes, tftpDownloadBytes, tftpFileDownloads, tftpFailedDownloads, tftpRetries }; } else { return new StatTracker[0]; } } // End of public get methods // Private get methods /* * Returns the amount of received byte data stored in the dataLength field. */ private int getDataLength() { return dataLength; } /* * Returns the opCode byte array second location. */ private byte getOpCode() { return opCode[1]; } /* * Returns the blockNumber byte array. */ private byte[] getBlockNumber() { return blockNumber; } /* * Returns the Actual number of bytes sent by the tftp server. */ private int getActualDownloadedBytes() { return totalActualDataLength; } // End of get methods. /* * Try to create a new socket, set the time out and return local port number. */ private int initialiseSocket() throws TFTPClientException { // If there are any problems throw exception try { socket = new DatagramSocket(); socket.setSoTimeout(getTimeout()); return socket.getLocalPort(); } catch(SocketException socketException) { if (socket != null && ! socket.isClosed()) socket.close(); throw new TFTPClientException("An error occurred while initialising the " + "UDP socket. The error is " + socketException.toString()); } } /* * Create send and receive datagram packets. Throw exception if server IP is * illegal. */ private void initialisePackets() throws TFTPClientException { byte[] buffer = new byte[MAX_BUFFER_SIZE]; try { sendPacket = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(getServerIP()), getRemotePort()); receivePacket = new DatagramPacket(buffer , buffer.length); } catch(UnknownHostException unknownHostException) { throw new TFTPClientException("IP address " + getServerIP() + " is invalid. The error is " + unknownHostException.toString()); } } /* * This method builds a new TFTP packet by adding the appropriate fields * to the tftpSendPacket byte array. Returns true if successful, * false if not. */ private boolean buildTftpPacket() { Byte errorCode; tftpSendPacket.reset(); switch (getOpCode()) { case PACKET_RRQ: case PACKET_WRQ: tftpSendPacket.write(opCode, 0, opCode.length); tftpSendPacket.write(getFileName().getBytes(), 0, getFileName().getBytes().length); tftpSendPacket.write(PACKET_DATA_TERMINATOR); tftpSendPacket.write(getDataTransferMode().getBytes(), 0, getDataTransferMode().getBytes().length); tftpSendPacket.write(PACKET_DATA_TERMINATOR); return true; case PACKET_ACK: tftpSendPacket.write(opCode, 0, opCode.length); tftpSendPacket.write(blockNumber, 0, blockNumber.length); return true; case PACKET_DATA: tftpSendPacket.write(opCode, 0, opCode.length); tftpSendPacket.write(blockNumber, 0, blockNumber.length); tftpSendPacket.write(data, 0, data.length); return true; case PACKET_ERROR: tftpSendPacket.write(opCode, 0, opCode.length); tftpSendPacket.write(getErrorCode(), 0, getErrorCode().length); errorCode = new Byte(getErrorCode()[1]); tftpSendPacket.write(ERROR_MESSAGES[errorCode.intValue()].getBytes(), 0, ERROR_MESSAGES[errorCode.intValue()].getBytes().length); tftpSendPacket.write(PACKET_DATA_TERMINATOR); return true; default: return false; } } /* * The method parses a tftp packet and disassembles it into its components */ private int parseTftpPacket() { String errorMessage; byte[] blockNumber = new byte[2]; byte[] oldBlockNumber; tftpReceivedPacket = new ByteArrayInputStream(receivePacket.getData(), 0, receivePacket.getLength()); tftpReceivedPacket.read(opCode, 0, 2); switch (opCode[1]) { case PACKET_ACK: tftpReceivedPacket.read(blockNumber, 0, 2); setBlockNumber(blockNumber); return 4; case PACKET_DATA: oldBlockNumber = getBlockNumber(); tftpReceivedPacket.read(blockNumber, 0, 2); setBlockNumber(blockNumber); // set current packet length setDataLength(tftpReceivedPacket.available()); // If block numbers are different then it is a new data packet if (! Arrays.equals(blockNumber, oldBlockNumber)) { // At this time these steps are unnecessary but may be useful in // the future data = new byte[getDataLength()]; tftpReceivedPacket.read(data, 0, data.length); // Add new data chunk length to the old value setTotalDataLength(getFetchedDataLength() + getDataLength()); // Add new data chunk length to the total overall download length setTotalActualDataLength(getActualDownloadedBytes() + getDataLength()); } else { // If it is a retransmit just add its length to the overall download length setTotalActualDataLength(getActualDownloadedBytes() + getDataLength()); } return 3; case PACKET_ERROR: tftpReceivedPacket.read(errorCode, 0, 2); setDataLength(tftpReceivedPacket.available() - 1); data = new byte[getDataLength()]; tftpReceivedPacket.read(data, 0, data.length); errorMessage = new String(data); setErrorMessage(errorMessage); return 5; case PACKET_RRQ: return 1; case PACKET_WRQ: return 2; default: return 6; } } /* * This method tries to send the TFTP packet over UDP. If it cannot * an exception is thrown. */ private void sendTftpPacket() throws TFTPClientException { byte[] data = tftpSendPacket.toByteArray(); try { sendPacket.setData(data, 0, data.length); if (sendPacket.getPort() != getRemotePort()) sendPacket.setPort(getRemotePort()); socket.send(sendPacket); } catch(IOException exception) { if (socket != null && ! socket.isClosed()) socket.close(); throw new TFTPClientException("Could not send packet. Error is " + exception.toString()); } } /* * This method tries to read a TFTP packet from the network. If a socket * time out occurs this method returns 1 without reading any packet */ private int readTftpPacket() throws TFTPClientException { int returnValue = 0; try { socket.receive(receivePacket); if (getRemotePort() != receivePacket.getPort()) setRemotePort(receivePacket.getPort()); } catch(SocketTimeoutException socketTimeoutException) { // If there is a socket time out then return 1 returnValue = 1; } catch(IOException exception) { // If problems raise an exception if (socket != null && ! socket.isClosed()) socket.close(); throw new TFTPClientException("Could not read packet. Error is " + exception.toString()); } return returnValue; } /** * Downloads the file specified by the <CODE>filename</CODE> field from the * TFTP server whose address is obtained from the <CODE>serverIP</CODE> * field. Method returns an integer to flag success or not. Method * maintaines statistics when the latter are enabled. * * @return 0 if everything was successfull, 1 if an error packet has been * received, and 2 if the maximum number of retransmitts has been * reached and an error packet has been sent. * * @throws TFTPClientException if there are errors during IO. */ public int getFile() throws TFTPClientException { int returnCode; if (areWeCollectingNow()) { tftpDownloadTime.startTimer(); } returnCode = getFileInternal(); if (areWeCollectingNow()) { tftpDownloadTime.stopTimer(); if (returnCode == 0) { tftpFileDownloads.increment(); tftpDownloadBytes.addValue(getFetchedDataLength()); tftpActualDownloadedBytes.addValue(getActualDownloadedBytes()); } else { tftpFailedDownloads.increment(); } } return returnCode; } /* * This method fetches a file from a TFTP server. */ private int getFileInternal() throws TFTPClientException { boolean finished = false; int returnCode = 0; int retriesCounter = 1; int readTftpPacketValue = 0; // Initialise socket and packets setLocalPort(initialiseSocket()); initialisePackets(); System.out.println("Trying to fetch file " + getFileName() + " from server " + getServerIP()); // Clear any previous received data receivedData.reset(); // Set op code to read request and set remote port to 69 setOpCode(PACKET_RRQ); setRemotePort(DEFAULT_TFTP_SERVER_PORT); setErrorMessage(ERROR_MESSAGES[8]); // Build TFTP Read request packet if (buildTftpPacket()) { // If packet has been built successfully, try to send it sendTftpPacket(); // Loop until we are finished while (! finished) { // Try to read packet from the network readTftpPacketValue = readTftpPacket(); switch (readTftpPacketValue) { case 0: // If successful, parse packet retriesCounter = 1; switch (parseTftpPacket()) { case 3: // Data packet // Check that data part is 512 bytes long if (getDataLength() == 512) { // Build an acknowledgement packet and send it back setOpCode(PACKET_ACK); if (buildTftpPacket()) // Send packet sendTftpPacket(); } else { // If data part is less then 512 bytes long then this is // the last data packet setOpCode(PACKET_ACK); // Build the last acknowledgement packet if (buildTftpPacket()) { // Send packet sendTftpPacket(); finished = true; } } break; case 5: // Error packet has been received finished = true; returnCode = 1; break; } break; case 1: // A socket timeout has occurred. Need to resend last packet // First check that the client has not exceeded the number of // retries if (retriesCounter < getNumberOfRetries()) { // Send packet sendTftpPacket(); retriesCounter++; // If statistics are enabled increment the retries counter if (collectStatistics) tftpRetries.increment(); } else { // If number of retries has been exceeded send an error packet setOpCode(PACKET_ERROR); setErrorCode(ERROR_NOT_DEFINED); if (buildTftpPacket()) { // Send packet sendTftpPacket(); // After sending the error packet terminate finished = true; returnCode = 2; } } break; } } } try { // Close socket. Ignore any thrown exceptions socket.close(); } catch (Exception e) {} return returnCode; } }