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