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; 027 028import org.apache.commons.net.io.FromNetASCIIOutputStream; 029import org.apache.commons.net.io.ToNetASCIIInputStream; 030 031/** 032 * The TFTPClient class encapsulates all the aspects of the TFTP protocol necessary to receive and send files through TFTP. It is derived from the 033 * {@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 034 * to deal with the TFTP protocol directly. However, almost every user should only be concerend with the the 035 * {@link org.apache.commons.net.DatagramSocketClient#open open() }, {@link org.apache.commons.net.DatagramSocketClient#close close() }, {@link #sendFile 036 * sendFile() }, and {@link #receiveFile receiveFile() } methods. Additionally, the {@link #setMaxTimeouts setMaxTimeouts() } and 037 * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } methods may be of importance for performance tuning. 038 * <p> 039 * 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 040 * worry about the internals. 041 * 042 * 043 * @see TFTP 044 * @see TFTPPacket 045 * @see TFTPPacketException 046 */ 047 048public class TFTPClient extends TFTP { 049 /** 050 * 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. 051 */ 052 public static final int DEFAULT_MAX_TIMEOUTS = 5; 053 054 /** The maximum number of timeouts allowed before failing. */ 055 private int maxTimeouts; 056 057 /** The number of bytes received in the ongoing download. */ 058 private long totalBytesReceived; 059 060 /** The number of bytes sent in the ongoing upload. */ 061 private long totalBytesSent; 062 063 /** 064 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, and buffered 065 * operations disabled. 066 */ 067 public TFTPClient() { 068 maxTimeouts = DEFAULT_MAX_TIMEOUTS; 069 } 070 071 /** 072 * Returns the maximum number of times a receive attempt is allowed to timeout before ending attempts to retry the receive and failing. 073 * 074 * @return The maximum number of timeouts allowed. 075 */ 076 public int getMaxTimeouts() { 077 return maxTimeouts; 078 } 079 080 /** 081 * @return The number of bytes received in the ongoing download 082 */ 083 public long getTotalBytesReceived() { 084 return totalBytesReceived; 085 } 086 087 /** 088 * @return The number of bytes sent in the ongoing download 089 */ 090 public long getTotalBytesSent() { 091 return totalBytesSent; 092 } 093 094 /** 095 * Same as calling receiveFile(fileName, mode, output, host, TFTP.DEFAULT_PORT). 096 * 097 * @param fileName The name of the file to receive. 098 * @param mode The TFTP mode of the transfer (one of the MODE constants). 099 * @param output The OutputStream to which the file should be written. 100 * @param host The remote host serving the file. 101 * @return number of bytes read 102 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 103 */ 104 public int receiveFile(final String fileName, final int mode, final OutputStream output, final InetAddress host) throws IOException { 105 return receiveFile(fileName, mode, output, host, DEFAULT_PORT); 106 } 107 108 /** 109 * 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 110 * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close 111 * the OutputStream containing the file; you must close it after the method invocation. 112 * 113 * @param fileName The name of the file to receive. 114 * @param mode The TFTP mode of the transfer (one of the MODE constants). 115 * @param output The OutputStream to which the file should be written. 116 * @param host The remote host serving the file. 117 * @param port The port number of the remote TFTP server. 118 * @return number of bytes read 119 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 120 */ 121 public int receiveFile(final String fileName, final int mode, OutputStream output, InetAddress host, final int port) throws IOException { 122 int bytesRead = 0; 123 int lastBlock = 0; 124 int block = 1; 125 int hostPort = 0; 126 int dataLength = 0; 127 128 totalBytesReceived = 0; 129 130 if (mode == TFTP.ASCII_MODE) { 131 output = new FromNetASCIIOutputStream(output); 132 } 133 134 TFTPPacket sent = new TFTPReadRequestPacket(host, port, fileName, mode); 135 final TFTPAckPacket ack = new TFTPAckPacket(host, port, 0); 136 137 beginBufferedOps(); 138 139 boolean justStarted = true; 140 try { 141 do { // while more data to fetch 142 bufferedSend(sent); // start the fetch/send an ack 143 boolean wantReply = true; 144 int timeouts = 0; 145 do { // until successful response 146 try { 147 final TFTPPacket received = bufferedReceive(); 148 // The first time we receive we get the port number and 149 // answering host address (for hosts with multiple IPs) 150 final int recdPort = received.getPort(); 151 final InetAddress recdAddress = received.getAddress(); 152 if (justStarted) { 153 justStarted = false; 154 if (recdPort == port) { // must not use the control port here 155 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT"); 156 bufferedSend(error); 157 throw new IOException("Incorrect source port (" + recdPort + ") in request reply."); 158 } 159 hostPort = recdPort; 160 ack.setPort(hostPort); 161 if (!host.equals(recdAddress)) { 162 host = recdAddress; 163 ack.setAddress(host); 164 sent.setAddress(host); 165 } 166 } 167 // Comply with RFC 783 indication that an error acknowledgment 168 // should be sent to originator if unexpected TID or host. 169 if (host.equals(recdAddress) && recdPort == hostPort) { 170 switch (received.getType()) { 171 172 case TFTPPacket.ERROR: 173 TFTPErrorPacket error = (TFTPErrorPacket) received; 174 throw new IOException("Error code " + error.getError() + " received: " + error.getMessage()); 175 case TFTPPacket.DATA: 176 final TFTPDataPacket data = (TFTPDataPacket) received; 177 dataLength = data.getDataLength(); 178 lastBlock = data.getBlockNumber(); 179 180 if (lastBlock == block) { // is the next block number? 181 try { 182 output.write(data.getData(), data.getDataOffset(), dataLength); 183 } catch (final IOException e) { 184 error = new TFTPErrorPacket(host, hostPort, TFTPErrorPacket.OUT_OF_SPACE, "File write failed."); 185 bufferedSend(error); 186 throw e; 187 } 188 ++block; 189 if (block > 65535) { 190 // wrap the block number 191 block = 0; 192 } 193 wantReply = false; // got the next block, drop out to ack it 194 } else { // unexpected block number 195 discardPackets(); 196 if (lastBlock == (block == 0 ? 65535 : block - 1)) { 197 wantReply = false; // Resend last acknowledgemen 198 } 199 } 200 break; 201 202 default: 203 throw new IOException("Received unexpected packet type (" + received.getType() + ")"); 204 } 205 } else { // incorrect host or TID 206 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port."); 207 bufferedSend(error); 208 } 209 } catch (final SocketException | InterruptedIOException e) { 210 if (++timeouts >= maxTimeouts) { 211 throw new IOException("Connection timed out."); 212 } 213 } catch (final TFTPPacketException e) { 214 throw new IOException("Bad packet: " + e.getMessage()); 215 } 216 } while (wantReply); // waiting for response 217 218 ack.setBlockNumber(lastBlock); 219 sent = ack; 220 bytesRead += dataLength; 221 totalBytesReceived += dataLength; 222 } while (dataLength == TFTPPacket.SEGMENT_SIZE); // not eof 223 bufferedSend(sent); // send the final ack 224 } finally { 225 endBufferedOps(); 226 } 227 return bytesRead; 228 } 229 230 /** 231 * Same as calling receiveFile(fileName, mode, output, hostname, TFTP.DEFAULT_PORT). 232 * 233 * @param fileName The name of the file to receive. 234 * @param mode The TFTP mode of the transfer (one of the MODE constants). 235 * @param output The OutputStream to which the file should be written. 236 * @param hostname The name of the remote host serving the file. 237 * @return number of bytes read 238 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 239 * @throws UnknownHostException If the hostname cannot be resolved. 240 */ 241 public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname) throws UnknownHostException, IOException { 242 return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), DEFAULT_PORT); 243 } 244 245 /** 246 * 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 247 * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close 248 * the OutputStream containing the file; you must close it after the method invocation. 249 * 250 * @param fileName The name of the file to receive. 251 * @param mode The TFTP mode of the transfer (one of the MODE constants). 252 * @param output The OutputStream to which the file should be written. 253 * @param hostname The name of the remote host serving the file. 254 * @param port The port number of the remote TFTP server. 255 * @return number of bytes read 256 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 257 * @throws UnknownHostException If the hostname cannot be resolved. 258 */ 259 public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname, final int port) 260 throws UnknownHostException, IOException { 261 return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), port); 262 } 263 264 /** 265 * Same as calling sendFile(fileName, mode, input, host, TFTP.DEFAULT_PORT). 266 * 267 * @param fileName The name the remote server should use when creating the file on its file system. 268 * @param mode The TFTP mode of the transfer (one of the MODE constants). 269 * @param input the input stream containing the data to be sent 270 * @param host The name of the remote host receiving the file. 271 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 272 * @throws UnknownHostException If the hostname cannot be resolved. 273 */ 274 public void sendFile(final String fileName, final int mode, final InputStream input, final InetAddress host) throws IOException { 275 sendFile(fileName, mode, input, host, DEFAULT_PORT); 276 } 277 278 /** 279 * 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 280 * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close 281 * the InputStream containing the file; you must close it after the method invocation. 282 * 283 * @param fileName The name the remote server should use when creating the file on its file system. 284 * @param mode The TFTP mode of the transfer (one of the MODE constants). 285 * @param input the input stream containing the data to be sent 286 * @param host The remote host receiving the file. 287 * @param port The port number of the remote TFTP server. 288 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 289 */ 290 public void sendFile(final String fileName, final int mode, InputStream input, InetAddress host, final int port) throws IOException { 291 int block = 0; 292 int hostPort = 0; 293 boolean justStarted = true; 294 boolean lastAckWait = false; 295 296 totalBytesSent = 0L; 297 298 if (mode == TFTP.ASCII_MODE) { 299 input = new ToNetASCIIInputStream(input); 300 } 301 302 TFTPPacket sent = new TFTPWriteRequestPacket(host, port, fileName, mode); 303 final TFTPDataPacket data = new TFTPDataPacket(host, port, 0, sendBuffer, 4, 0); 304 305 beginBufferedOps(); 306 307 try { 308 do { // until eof 309 // first time: block is 0, lastBlock is 0, send a request packet. 310 // subsequent: block is integer starting at 1, send data packet. 311 bufferedSend(sent); 312 boolean wantReply = true; 313 int timeouts = 0; 314 do { 315 try { 316 final TFTPPacket received = bufferedReceive(); 317 final InetAddress recdAddress = received.getAddress(); 318 final int recdPort = received.getPort(); 319 // The first time we receive we get the port number and 320 // answering host address (for hosts with multiple IPs) 321 if (justStarted) { 322 justStarted = false; 323 if (recdPort == port) { // must not use the control port here 324 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT"); 325 bufferedSend(error); 326 throw new IOException("Incorrect source port (" + recdPort + ") in request reply."); 327 } 328 hostPort = recdPort; 329 data.setPort(hostPort); 330 if (!host.equals(recdAddress)) { 331 host = recdAddress; 332 data.setAddress(host); 333 sent.setAddress(host); 334 } 335 } 336 // Comply with RFC 783 indication that an error acknowledgment 337 // should be sent to originator if unexpected TID or host. 338 if (host.equals(recdAddress) && recdPort == hostPort) { 339 340 switch (received.getType()) { 341 case TFTPPacket.ERROR: 342 final TFTPErrorPacket error = (TFTPErrorPacket) received; 343 throw new IOException("Error code " + error.getError() + " received: " + error.getMessage()); 344 case TFTPPacket.ACKNOWLEDGEMENT: 345 346 final int lastBlock = ((TFTPAckPacket) received).getBlockNumber(); 347 348 if (lastBlock == block) { 349 ++block; 350 if (block > 65535) { 351 // wrap the block number 352 block = 0; 353 } 354 wantReply = false; // got the ack we want 355 } else { 356 discardPackets(); 357 } 358 break; 359 default: 360 throw new IOException("Received unexpected packet type."); 361 } 362 } else { // wrong host or TID; send error 363 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port."); 364 bufferedSend(error); 365 } 366 } catch (final SocketException | InterruptedIOException e) { 367 if (++timeouts >= maxTimeouts) { 368 throw new IOException("Connection timed out."); 369 } 370 } catch (final TFTPPacketException e) { 371 throw new IOException("Bad packet: " + e.getMessage()); 372 } 373 // retry until a good ack 374 } while (wantReply); 375 376 if (lastAckWait) { 377 break; // we were waiting for this; now all done 378 } 379 380 int dataLength = TFTPPacket.SEGMENT_SIZE; 381 int offset = 4; 382 int totalThisPacket = 0; 383 int bytesRead = 0; 384 while (dataLength > 0 && (bytesRead = input.read(sendBuffer, offset, dataLength)) > 0) { 385 offset += bytesRead; 386 dataLength -= bytesRead; 387 totalThisPacket += bytesRead; 388 } 389 if (totalThisPacket < TFTPPacket.SEGMENT_SIZE) { 390 /* this will be our last packet -- send, wait for ack, stop */ 391 lastAckWait = true; 392 } 393 data.setBlockNumber(block); 394 data.setData(sendBuffer, 4, totalThisPacket); 395 sent = data; 396 totalBytesSent += totalThisPacket; 397 } while (true); // loops until after lastAckWait is set 398 } finally { 399 endBufferedOps(); 400 } 401 } 402 403 /** 404 * Same as calling sendFile(fileName, mode, input, hostname, TFTP.DEFAULT_PORT). 405 * 406 * @param fileName The name the remote server should use when creating the file on its file system. 407 * @param mode The TFTP mode of the transfer (one of the MODE constants). 408 * @param input the input stream containing the data to be sent 409 * @param hostname The name of the remote host receiving the file. 410 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 411 * @throws UnknownHostException If the hostname cannot be resolved. 412 */ 413 public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname) throws UnknownHostException, IOException { 414 sendFile(fileName, mode, input, InetAddress.getByName(hostname), DEFAULT_PORT); 415 } 416 417 /** 418 * 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 419 * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close 420 * the InputStream containing the file; you must close it after the method invocation. 421 * 422 * @param fileName The name the remote server should use when creating the file on its file system. 423 * @param mode The TFTP mode of the transfer (one of the MODE constants). 424 * @param input the input stream containing the data to be sent 425 * @param hostname The name of the remote host receiving the file. 426 * @param port The port number of the remote TFTP server. 427 * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. 428 * @throws UnknownHostException If the hostname cannot be resolved. 429 */ 430 public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname, final int port) 431 throws UnknownHostException, IOException { 432 sendFile(fileName, mode, input, InetAddress.getByName(hostname), port); 433 } 434 435 /** 436 * Sets the maximum number of times a receive attempt is allowed to timeout during a receiveFile() or sendFile() operation before ending attempts to retry 437 * the receive and failing. The default is DEFAULT_MAX_TIMEOUTS. 438 * 439 * @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. 440 */ 441 public void setMaxTimeouts(final int numTimeouts) { 442 maxTimeouts = Math.max(numTimeouts, 1); 443 } 444}