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