blob: dcbea95d39ec6a988c0353d384cd335f09379889 [file]
/***
* $Id: TFTPClient.java,v 1.1 2002/04/03 01:04:39 brekke Exp $
*
* NetComponents Internet Protocol Library
* Copyright (C) 1997-2002 Daniel F. Savarese
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library in the LICENSE file; if not, write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
***/
package com.oroinc.net.tftp;
import java.io.*;
import java.net.*;
import com.oroinc.io.*;
/***
* The TFTPClient class encapsulates all the aspects of the TFTP protocol
* necessary to receive and send files through TFTP. It is derived from
* the <a href="com.oroinc.net.tftp.TFTP.html"> TFTP class </a> because
* it is more convenient than using aggregation, and as a result exposes
* the same set of methods to allow you to deal with the TFTP protocol
* directly. However, almost every user should only be concerend with the
* the <a href="com.oroinc.net.DatagramSocketClient.html#open"> open() </a>,
* <a href="com.oroinc.net.DatagramSocketClient.html#close"> close() </a>,
* <a href="#sendFile"> sendFile() </a>, and
* <a href="#receiveFile"> receiveFile() </a> methods. Additionally, the
* <a href="#setMaxTimeouts"> setMaxTimeouts() </a> and
* <a href="com.oroinc.net.DatagramSocketClient.html#setDefaultTimeout">
* setDefaultTimeout() </a> methods may be of importance for performance
* tuning.
* <p>
* 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 worry about the internals.
* <p>
* <p>
* @author Daniel F. Savarese
* @see TFTP
* @see TFTPPacket
* @see TFTPPacketException
***/
public class TFTPClient extends TFTP {
/***
* The default number of times a receive attempt is allowed to timeout
* before ending attempts to retry the receive and failing. The default
* is 5 timeouts.
***/
public static final int DEFAULT_MAX_TIMEOUTS = 5;
/*** The maximum number of timeouts allowed before failing. ***/
private int __maxTimeouts;
/***
* Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
* maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
* and buffered operations disabled.
***/
public TFTPClient() {
__maxTimeouts = DEFAULT_MAX_TIMEOUTS;
}
/***
* Sets the maximum number of times a receive attempt is allowed to
* timeout during a receiveFile() or sendFile() operation before ending
* attempts to retry the receive and failing.
* The default is DEFAULT_MAX_TIMEOUTS.
* <p>
* @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.
***/
public void setMaxTimeouts(int numTimeouts) {
if(__maxTimeouts < 1)
__maxTimeouts = 1;
else
__maxTimeouts = numTimeouts;
}
/***
* Returns the maximum number of times a receive attempt is allowed to
* timeout before ending attempts to retry the receive and failing.
* <p>
* @return The maximum number of timeouts allowed.
***/
public int getMaxTimeouts() { return __maxTimeouts; }
/***
* 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 socket must first be created by
* <a href="com.oroinc.net.DatagramSocketClient.html#open">open()</a> before
* invoking this method. This method will not close the OutputStream
* containing the file; you must close it after the method invocation.
* <p>
* @param filename The name of the file to receive.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The OutputStream to which the file should be written.
* @param host The remote host serving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
***/
public int receiveFile(String filename, int mode, OutputStream output,
InetAddress host, int port) throws IOException
{
int bytesRead, timeouts, lastBlock, block, hostPort, dataLength;
TFTPPacket sent, received = null;
TFTPErrorPacket error;
TFTPDataPacket data;
TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
beginBufferedOps();
dataLength = lastBlock = hostPort = bytesRead = 0;
block = 1;
if(mode == TFTP.ASCII_MODE)
output = new FromNetASCIIOutputStream(output);
sent =
new TFTPReadRequestPacket(host, port, filename, mode);
_sendPacket:
do {
bufferedSend(sent);
_receivePacket:
while(true) {
timeouts = 0;
while(timeouts < __maxTimeouts) {
try {
//System.err.println("Receiving.");
received = bufferedReceive();
break;
} catch(SocketException e) {
if(++timeouts >= __maxTimeouts) {
endBufferedOps();
throw new IOException("Connection timed out.");
}
continue;
} catch(InterruptedIOException e) {
if(++timeouts >= __maxTimeouts) {
endBufferedOps();
throw new IOException("Connection timed out.");
}
continue;
} catch(TFTPPacketException e) {
endBufferedOps();
throw new IOException("Bad packet: " + e.getMessage());
}
}
//System.err.println("Got an answer.");
// The first time we receive we get the port number.
if(lastBlock == 0) {
hostPort = received.getPort();
ack.setPort(hostPort);
}
// Comply with RFC 783 indication that an error acknowledgement
// should be sent to originator if unexpected TID or host.
if(host.equals(received.getAddress()) &&
received.getPort() == hostPort) {
//System.err.println("Got this far.");
switch(received.getType()) {
case TFTPPacket.ERROR:
error = (TFTPErrorPacket)received;
endBufferedOps();
throw new IOException("Error code " + error.getError() +
" received: " + error.getMessage());
case TFTPPacket.DATA:
data = (TFTPDataPacket)received;
dataLength = data.getDataLength();
lastBlock = data.getBlockNumber();
//System.err.println("Data: Got Block: " + lastBlock +" Expected: " + block);
if(lastBlock == block) {
try {
output.write(data.getData(), data.getDataOffset(),
dataLength);
} catch(IOException e){
error = new TFTPErrorPacket(host, hostPort,
TFTPErrorPacket.OUT_OF_SPACE,
"File write failed.");
bufferedSend(error);
endBufferedOps();
throw e;
}
++block;
break _receivePacket;
} else {
discardPackets();
if(lastBlock == (block - 1))
continue _sendPacket; // Resend last acknowledgement.
continue _receivePacket; // Start fetching packets again.
}
//break;
default:
endBufferedOps();
throw new IOException("Received unexpected packet type.");
}
} else {
error = new TFTPErrorPacket(received.getAddress(),
received.getPort(),
TFTPErrorPacket.UNKNOWN_TID,
"Unexpected host or port.");
bufferedSend(error);
continue _sendPacket;
}
// We should never get here, but this is a safety to avoid
// infinite loop. If only Java had the goto statement.
//break;
}
//System.err.println("Setting acknowledgement: " + lastBlock);
ack.setBlockNumber(lastBlock);
sent = ack;
bytesRead+=dataLength;
//System.err.println("bytesRead: " + bytesRead);
}// First data packet less than 512 bytes signals end of stream.
while(dataLength == TFTPPacket.SEGMENT_SIZE);
bufferedSend(sent);
endBufferedOps();
return bytesRead;
}
/***
* 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 socket must first be created by
* <a href="com.oroinc.net.DatagramSocketClient.html#open">open()</a> before
* invoking this method. This method will not close the OutputStream
* containing the file; you must close it after the method invocation.
* <p>
* @param filename The name of the file to receive.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The OutputStream to which the file should be written.
* @param hostname The name of the remote host serving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
* @exception UnknownHostException If the hostname cannot be resolved.
***/
public int receiveFile(String filename, int mode, OutputStream output,
String hostname, int port)
throws UnknownHostException, IOException
{
return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
port);
}
/***
* Same as calling
* <code> receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT)
* </code> <p>
* @param filename The name of the file to receive.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The OutputStream to which the file should be written.
* @param host The remote host serving the file.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
***/
public int receiveFile(String filename, int mode, OutputStream output,
InetAddress host)
throws IOException
{
return receiveFile(filename, mode, output, host, DEFAULT_PORT);
}
/***
* Same as calling
* <code> receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT)
* </code> <p>
* @param filename The name of the file to receive.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The OutputStream to which the file should be written.
* @param hostname The name of the remote host serving the file.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
* @exception UnknownHostException If the hostname cannot be resolved.
***/
public int receiveFile(String filename, int mode, OutputStream output,
String hostname)
throws UnknownHostException, IOException
{
return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
DEFAULT_PORT);
}
/***
* 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 socket must first be created by
* <a href="com.oroinc.net.DatagramSocketClient.html#open">open()</a> before
* invoking this method. This method will not close the InputStream
* containing the file; you must close it after the method invocation.
* <p>
* @param filename The name the remote server should use when creating
* the file on its file system.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The InputStream containing the file.
* @param host The remote host receiving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
***/
public void sendFile(String filename, int mode, InputStream input,
InetAddress host, int port) throws IOException {
int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset;
TFTPPacket sent, received = null;
TFTPErrorPacket error;
TFTPDataPacket data =
new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);;
TFTPAckPacket ack;
beginBufferedOps();
dataLength = lastBlock = hostPort = bytesRead = 0;
block = 0;
if(mode == TFTP.ASCII_MODE)
input = new ToNetASCIIInputStream(input);
sent =
new TFTPWriteRequestPacket(host, port, filename, mode);
_sendPacket:
do {
bufferedSend(sent);
_receivePacket:
while(true) {
timeouts = 0;
while(timeouts < __maxTimeouts) {
try {
//System.err.println("Receiving.");
received = bufferedReceive();
break;
} catch(SocketException e) {
if(++timeouts >= __maxTimeouts) {
endBufferedOps();
throw new IOException("Connection timed out.");
}
continue;
} catch(InterruptedIOException e) {
if(++timeouts >= __maxTimeouts) {
endBufferedOps();
throw new IOException("Connection timed out.");
}
continue;
} catch(TFTPPacketException e) {
endBufferedOps();
throw new IOException("Bad packet: " + e.getMessage());
}
}
//System.err.println("Got an answer.");
// The first time we receive we get the port number.
if(lastBlock == 0) {
hostPort = received.getPort();
data.setPort(hostPort);
}
// Comply with RFC 783 indication that an error acknowledgement
// should be sent to originator if unexpected TID or host.
if(host.equals(received.getAddress()) &&
received.getPort() == hostPort) {
//System.err.println("Got this far.");
switch(received.getType()) {
case TFTPPacket.ERROR:
error = (TFTPErrorPacket)received;
endBufferedOps();
throw new IOException("Error code " + error.getError() +
" received: " + error.getMessage());
case TFTPPacket.ACKNOWLEDGEMENT:
ack = (TFTPAckPacket)received;
lastBlock = ack.getBlockNumber();
//System.err.println("Ack: Got Block: " + lastBlock +" Expected: " + block);
if(lastBlock == block) {
++block;
break _receivePacket;
} else {
discardPackets();
if(lastBlock == (block - 1))
continue _sendPacket; // Resend last acknowledgement.
continue _receivePacket; // Start fetching packets again.
}
//break;
default:
endBufferedOps();
throw new IOException("Received unexpected packet type.");
}
} else {
error = new TFTPErrorPacket(received.getAddress(),
received.getPort(),
TFTPErrorPacket.UNKNOWN_TID,
"Unexpected host or port.");
bufferedSend(error);
continue _sendPacket;
}
// We should never get here, but this is a safety to avoid
// infinite loop. If only Java had the goto statement.
//break;
}
dataLength = TFTPPacket.SEGMENT_SIZE;
offset = 4;
while(dataLength > 0 &&
(bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0) {
offset+=bytesRead;
dataLength-=bytesRead;
}
data.setBlockNumber(block);
data.setData(_sendBuffer, 4, offset - 4);
sent = data;
} while(dataLength == 0);
bufferedSend(sent);
endBufferedOps();
}
/***
* 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 socket must first be created by
* <a href="com.oroinc.net.DatagramSocketClient.html#open">open()</a> before
* invoking this method. This method will not close the InputStream
* containing the file; you must close it after the method invocation.
* <p>
* @param filename The name the remote server should use when creating
* the file on its file system.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The InputStream containing the file.
* @param hostname The name of the remote host receiving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
* @exception UnknownHostException If the hostname cannot be resolved.
***/
public void sendFile(String filename, int mode, InputStream input,
String hostname, int port)
throws UnknownHostException, IOException
{
sendFile(filename, mode, input, InetAddress.getByName(hostname), port);
}
/***
* Same as calling
* <code> sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT); </code>
* <p>
* @param filename The name the remote server should use when creating
* the file on its file system.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The InputStream containing the file.
* @param hostname The name of the remote host receiving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
* @exception UnknownHostException If the hostname cannot be resolved.
***/
public void sendFile(String filename, int mode, InputStream input,
InetAddress host)
throws IOException
{
sendFile(filename, mode, input, host, DEFAULT_PORT);
}
/***
* Same as calling
* <code> sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT);
* </code> <p>
* @param filename The name the remote server should use when creating
* the file on its file system.
* @param mode The TFTP mode of the transfer (one of the MODE constants).
* @param output The InputStream containing the file.
* @param hostname The name of the remote host receiving the file.
* @param port The port number of the remote TFTP server.
* @exception IOException If an I/O error occurs. The nature of the
* error will be reported in the message.
* @exception UnknownHostException If the hostname cannot be resolved.
***/
public void sendFile(String filename, int mode, InputStream input,
String hostname)
throws UnknownHostException, IOException
{
sendFile(filename, mode, input, InetAddress.getByName(hostname),
DEFAULT_PORT);
}
}