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