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 018 package org.apache.commons.net.tftp; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.InterruptedIOException; 023 import java.io.OutputStream; 024 import java.net.InetAddress; 025 import java.net.SocketException; 026 import java.net.UnknownHostException; 027 import org.apache.commons.net.io.FromNetASCIIOutputStream; 028 import 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 * <p> 050 * <p> 051 * @see TFTP 052 * @see TFTPPacket 053 * @see TFTPPacketException 054 ***/ 055 056 public 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 /*** 069 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, 070 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, 071 * and buffered operations disabled. 072 ***/ 073 public TFTPClient() 074 { 075 __maxTimeouts = DEFAULT_MAX_TIMEOUTS; 076 } 077 078 /*** 079 * Sets the maximum number of times a receive attempt is allowed to 080 * timeout during a receiveFile() or sendFile() operation before ending 081 * attempts to retry the receive and failing. 082 * The default is DEFAULT_MAX_TIMEOUTS. 083 * <p> 084 * @param numTimeouts The maximum number of timeouts to allow. Values 085 * less than 1 should not be used, but if they are, they are 086 * treated as 1. 087 ***/ 088 public void setMaxTimeouts(int numTimeouts) 089 { 090 if (numTimeouts < 1) { 091 __maxTimeouts = 1; 092 } else { 093 __maxTimeouts = numTimeouts; 094 } 095 } 096 097 /*** 098 * Returns the maximum number of times a receive attempt is allowed to 099 * timeout before ending attempts to retry the receive and failing. 100 * <p> 101 * @return The maximum number of timeouts allowed. 102 ***/ 103 public int getMaxTimeouts() 104 { 105 return __maxTimeouts; 106 } 107 108 109 /*** 110 * Requests a named file from a remote host, writes the 111 * file to an OutputStream, closes the connection, and returns the number 112 * of bytes read. A local UDP socket must first be created by 113 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 114 * invoking this method. This method will not close the OutputStream 115 * containing the file; you must close it after the method invocation. 116 * <p> 117 * @param filename The name of the file to receive. 118 * @param mode The TFTP mode of the transfer (one of the MODE constants). 119 * @param output The OutputStream to which the file should be written. 120 * @param host The remote host serving the file. 121 * @param port The port number of the remote TFTP server. 122 * @exception IOException If an I/O error occurs. The nature of the 123 * error will be reported in the message. 124 ***/ 125 public int receiveFile(String filename, int mode, OutputStream output, 126 InetAddress host, int port) throws IOException 127 { 128 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength; 129 TFTPPacket sent, received = null; 130 TFTPErrorPacket error; 131 TFTPDataPacket data; 132 TFTPAckPacket ack = new TFTPAckPacket(host, port, 0); 133 134 beginBufferedOps(); 135 136 dataLength = lastBlock = hostPort = bytesRead = 0; 137 block = 1; 138 139 if (mode == TFTP.ASCII_MODE) { 140 output = new FromNetASCIIOutputStream(output); 141 } 142 143 sent = 144 new TFTPReadRequestPacket(host, port, filename, mode); 145 146 _sendPacket: 147 do 148 { 149 bufferedSend(sent); 150 151 _receivePacket: 152 while (true) 153 { 154 timeouts = 0; 155 do { 156 try 157 { 158 received = bufferedReceive(); 159 break; 160 } 161 catch (SocketException e) 162 { 163 if (++timeouts >= __maxTimeouts) 164 { 165 endBufferedOps(); 166 throw new IOException("Connection timed out."); 167 } 168 continue _sendPacket; 169 } 170 catch (InterruptedIOException e) 171 { 172 if (++timeouts >= __maxTimeouts) 173 { 174 endBufferedOps(); 175 throw new IOException("Connection timed out."); 176 } 177 continue _sendPacket; 178 } 179 catch (TFTPPacketException e) 180 { 181 endBufferedOps(); 182 throw new IOException("Bad packet: " + e.getMessage()); 183 } 184 } while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once 185 186 // The first time we receive we get the port number and 187 // answering host address (for hosts with multiple IPs) 188 if (lastBlock == 0) 189 { 190 hostPort = received.getPort(); 191 ack.setPort(hostPort); 192 if(!host.equals(received.getAddress())) 193 { 194 host = received.getAddress(); 195 ack.setAddress(host); 196 sent.setAddress(host); 197 } 198 } 199 200 // Comply with RFC 783 indication that an error acknowledgement 201 // should be sent to originator if unexpected TID or host. 202 if (host.equals(received.getAddress()) && 203 received.getPort() == hostPort) 204 { 205 206 switch (received.getType()) 207 { 208 case TFTPPacket.ERROR: 209 error = (TFTPErrorPacket)received; 210 endBufferedOps(); 211 throw new IOException("Error code " + error.getError() + 212 " received: " + error.getMessage()); 213 case TFTPPacket.DATA: 214 data = (TFTPDataPacket)received; 215 dataLength = data.getDataLength(); 216 217 lastBlock = data.getBlockNumber(); 218 219 if (lastBlock == block) 220 { 221 try 222 { 223 output.write(data.getData(), data.getDataOffset(), 224 dataLength); 225 } 226 catch (IOException e) 227 { 228 error = new TFTPErrorPacket(host, hostPort, 229 TFTPErrorPacket.OUT_OF_SPACE, 230 "File write failed."); 231 bufferedSend(error); 232 endBufferedOps(); 233 throw e; 234 } 235 ++block; 236 if (block > 65535) 237 { 238 // wrap the block number 239 block = 0; 240 } 241 242 break _receivePacket; 243 } 244 else 245 { 246 discardPackets(); 247 248 if (lastBlock == (block == 0 ? 65535 : (block - 1))) { 249 continue _sendPacket; // Resend last acknowledgement. 250 } 251 252 continue _receivePacket; // Start fetching packets again. 253 } 254 //break; 255 256 default: 257 endBufferedOps(); 258 throw new IOException("Received unexpected packet type."); 259 } 260 } 261 else 262 { 263 error = new TFTPErrorPacket(received.getAddress(), 264 received.getPort(), 265 TFTPErrorPacket.UNKNOWN_TID, 266 "Unexpected host or port."); 267 bufferedSend(error); 268 continue _sendPacket; 269 } 270 271 // We should never get here, but this is a safety to avoid 272 // infinite loop. If only Java had the goto statement. 273 //break; 274 } 275 276 ack.setBlockNumber(lastBlock); 277 sent = ack; 278 bytesRead += dataLength; 279 } // First data packet less than 512 bytes signals end of stream. 280 281 while (dataLength == TFTPPacket.SEGMENT_SIZE); 282 283 bufferedSend(sent); 284 endBufferedOps(); 285 286 return bytesRead; 287 } 288 289 290 /*** 291 * Requests a named file from a remote host, writes the 292 * file to an OutputStream, closes the connection, and returns the number 293 * of bytes read. A local UDP socket must first be created by 294 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 295 * invoking this method. This method will not close the OutputStream 296 * containing the file; you must close it after the method invocation. 297 * <p> 298 * @param filename The name of the file to receive. 299 * @param mode The TFTP mode of the transfer (one of the MODE constants). 300 * @param output The OutputStream to which the file should be written. 301 * @param hostname The name of the remote host serving the file. 302 * @param port The port number of the remote TFTP server. 303 * @exception IOException If an I/O error occurs. The nature of the 304 * error will be reported in the message. 305 * @exception UnknownHostException If the hostname cannot be resolved. 306 ***/ 307 public int receiveFile(String filename, int mode, OutputStream output, 308 String hostname, int port) 309 throws UnknownHostException, IOException 310 { 311 return receiveFile(filename, mode, output, InetAddress.getByName(hostname), 312 port); 313 } 314 315 316 /*** 317 * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT). 318 * 319 * @param filename The name of the file to receive. 320 * @param mode The TFTP mode of the transfer (one of the MODE constants). 321 * @param output The OutputStream to which the file should be written. 322 * @param host The remote host serving the file. 323 * @exception IOException If an I/O error occurs. The nature of the 324 * error will be reported in the message. 325 ***/ 326 public int receiveFile(String filename, int mode, OutputStream output, 327 InetAddress host) 328 throws IOException 329 { 330 return receiveFile(filename, mode, output, host, DEFAULT_PORT); 331 } 332 333 /*** 334 * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT). 335 * 336 * @param filename The name of the file to receive. 337 * @param mode The TFTP mode of the transfer (one of the MODE constants). 338 * @param output The OutputStream to which the file should be written. 339 * @param hostname The name of the remote host serving the file. 340 * @exception IOException If an I/O error occurs. The nature of the 341 * error will be reported in the message. 342 * @exception UnknownHostException If the hostname cannot be resolved. 343 ***/ 344 public int receiveFile(String filename, int mode, OutputStream output, 345 String hostname) 346 throws UnknownHostException, IOException 347 { 348 return receiveFile(filename, mode, output, InetAddress.getByName(hostname), 349 DEFAULT_PORT); 350 } 351 352 353 /*** 354 * Requests to send a file to a remote host, reads the file from an 355 * InputStream, sends the file to the remote host, and closes the 356 * connection. A local UDP socket must first be created by 357 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 358 * invoking this method. This method will not close the InputStream 359 * containing the file; you must close it after the method invocation. 360 * <p> 361 * @param filename The name the remote server should use when creating 362 * the file on its file system. 363 * @param mode The TFTP mode of the transfer (one of the MODE constants). 364 * @param host The remote host receiving the file. 365 * @param port The port number of the remote TFTP server. 366 * @exception IOException If an I/O error occurs. The nature of the 367 * error will be reported in the message. 368 ***/ 369 public void sendFile(String filename, int mode, InputStream input, 370 InetAddress host, int port) throws IOException 371 { 372 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket; 373 TFTPPacket sent, received = null; 374 TFTPErrorPacket error; 375 TFTPDataPacket data = 376 new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0); 377 TFTPAckPacket ack; 378 379 boolean justStarted = true; 380 381 beginBufferedOps(); 382 383 dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0; 384 block = 0; 385 boolean lastAckWait = false; 386 387 if (mode == TFTP.ASCII_MODE) { 388 input = new ToNetASCIIInputStream(input); 389 } 390 391 sent = 392 new TFTPWriteRequestPacket(host, port, filename, mode); 393 394 _sendPacket: 395 do 396 { 397 // first time: block is 0, lastBlock is 0, send a request packet. 398 // subsequent: block is integer starting at 1, send data packet. 399 bufferedSend(sent); 400 401 // this is trying to receive an ACK 402 _receivePacket: 403 while (true) 404 { 405 406 407 timeouts = 0; 408 do { 409 try 410 { 411 received = bufferedReceive(); 412 break; 413 } 414 catch (SocketException e) 415 { 416 if (++timeouts >= __maxTimeouts) 417 { 418 endBufferedOps(); 419 throw new IOException("Connection timed out."); 420 } 421 continue _sendPacket; 422 } 423 catch (InterruptedIOException e) 424 { 425 if (++timeouts >= __maxTimeouts) 426 { 427 endBufferedOps(); 428 throw new IOException("Connection timed out."); 429 } 430 continue _sendPacket; 431 } 432 catch (TFTPPacketException e) 433 { 434 endBufferedOps(); 435 throw new IOException("Bad packet: " + e.getMessage()); 436 } 437 } // end of while loop over tries to receive 438 while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once 439 440 441 // The first time we receive we get the port number and 442 // answering host address (for hosts with multiple IPs) 443 if (justStarted) 444 { 445 justStarted = false; 446 hostPort = received.getPort(); 447 data.setPort(hostPort); 448 if(!host.equals(received.getAddress())) 449 { 450 host = received.getAddress(); 451 data.setAddress(host); 452 sent.setAddress(host); 453 } 454 } 455 456 // Comply with RFC 783 indication that an error acknowledgement 457 // should be sent to originator if unexpected TID or host. 458 if (host.equals(received.getAddress()) && 459 received.getPort() == hostPort) 460 { 461 462 switch (received.getType()) 463 { 464 case TFTPPacket.ERROR: 465 error = (TFTPErrorPacket)received; 466 endBufferedOps(); 467 throw new IOException("Error code " + error.getError() + 468 " received: " + error.getMessage()); 469 case TFTPPacket.ACKNOWLEDGEMENT: 470 ack = (TFTPAckPacket)received; 471 472 lastBlock = ack.getBlockNumber(); 473 474 if (lastBlock == block) 475 { 476 ++block; 477 if (block > 65535) 478 { 479 // wrap the block number 480 block = 0; 481 } 482 if (lastAckWait) { 483 484 break _sendPacket; 485 } 486 else { 487 break _receivePacket; 488 } 489 } 490 else 491 { 492 discardPackets(); 493 494 continue _receivePacket; // Start fetching packets again. 495 } 496 //break; 497 498 default: 499 endBufferedOps(); 500 throw new IOException("Received unexpected packet type."); 501 } 502 } 503 else 504 { 505 error = new TFTPErrorPacket(received.getAddress(), 506 received.getPort(), 507 TFTPErrorPacket.UNKNOWN_TID, 508 "Unexpected host or port."); 509 bufferedSend(error); 510 continue _sendPacket; 511 } 512 513 // We should never get here, but this is a safety to avoid 514 // infinite loop. If only Java had the goto statement. 515 //break; 516 } 517 518 // OK, we have just gotten ACK about the last data we sent. Make another 519 // and send it 520 521 dataLength = TFTPPacket.SEGMENT_SIZE; 522 offset = 4; 523 totalThisPacket = 0; 524 while (dataLength > 0 && 525 (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0) 526 { 527 offset += bytesRead; 528 dataLength -= bytesRead; 529 totalThisPacket += bytesRead; 530 } 531 532 if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) { 533 /* this will be our last packet -- send, wait for ack, stop */ 534 lastAckWait = true; 535 } 536 data.setBlockNumber(block); 537 data.setData(_sendBuffer, 4, totalThisPacket); 538 sent = data; 539 } 540 while ( totalThisPacket > 0 || lastAckWait ); 541 // Note: this was looping while dataLength == 0 || lastAckWait, 542 // which was discarding the last packet if it was not full size 543 // Should send the packet. 544 545 endBufferedOps(); 546 } 547 548 549 /*** 550 * Requests to send a file to a remote host, reads the file from an 551 * InputStream, sends the file to the remote host, and closes the 552 * connection. A local UDP socket must first be created by 553 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 554 * invoking this method. This method will not close the InputStream 555 * containing the file; you must close it after the method invocation. 556 * <p> 557 * @param filename The name the remote server should use when creating 558 * the file on its file system. 559 * @param mode The TFTP mode of the transfer (one of the MODE constants). 560 * @param hostname The name of the remote host receiving the file. 561 * @param port The port number of the remote TFTP server. 562 * @exception IOException If an I/O error occurs. The nature of the 563 * error will be reported in the message. 564 * @exception UnknownHostException If the hostname cannot be resolved. 565 ***/ 566 public void sendFile(String filename, int mode, InputStream input, 567 String hostname, int port) 568 throws UnknownHostException, IOException 569 { 570 sendFile(filename, mode, input, InetAddress.getByName(hostname), port); 571 } 572 573 574 /*** 575 * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT). 576 * 577 * @param filename The name the remote server should use when creating 578 * the file on its file system. 579 * @param mode The TFTP mode of the transfer (one of the MODE constants). 580 * @param host The name of the remote host receiving the file. 581 * @exception IOException If an I/O error occurs. The nature of the 582 * error will be reported in the message. 583 * @exception UnknownHostException If the hostname cannot be resolved. 584 ***/ 585 public void sendFile(String filename, int mode, InputStream input, 586 InetAddress host) 587 throws IOException 588 { 589 sendFile(filename, mode, input, host, DEFAULT_PORT); 590 } 591 592 /*** 593 * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT). 594 * 595 * @param filename The name the remote server should use when creating 596 * the file on its file system. 597 * @param mode The TFTP mode of the transfer (one of the MODE constants). 598 * @param hostname The name of the remote host receiving the file. 599 * @exception IOException If an I/O error occurs. The nature of the 600 * error will be reported in the message. 601 * @exception UnknownHostException If the hostname cannot be resolved. 602 ***/ 603 public void sendFile(String filename, int mode, InputStream input, 604 String hostname) 605 throws UnknownHostException, IOException 606 { 607 sendFile(filename, mode, input, InetAddress.getByName(hostname), 608 DEFAULT_PORT); 609 } 610 }