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