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 * @author Daniel F. Savarese 052 * @see TFTP 053 * @see TFTPPacket 054 * @see TFTPPacketException 055 ***/ 056 057 public class TFTPClient extends TFTP 058 { 059 /*** 060 * The default number of times a receive attempt is allowed to timeout 061 * before ending attempts to retry the receive and failing. The default 062 * is 5 timeouts. 063 ***/ 064 public static final int DEFAULT_MAX_TIMEOUTS = 5; 065 066 /*** The maximum number of timeouts allowed before failing. ***/ 067 private int __maxTimeouts; 068 069 /*** 070 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, 071 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, 072 * and buffered operations disabled. 073 ***/ 074 public TFTPClient() 075 { 076 __maxTimeouts = DEFAULT_MAX_TIMEOUTS; 077 } 078 079 /*** 080 * Sets the maximum number of times a receive attempt is allowed to 081 * timeout during a receiveFile() or sendFile() operation before ending 082 * attempts to retry the receive and failing. 083 * The default is DEFAULT_MAX_TIMEOUTS. 084 * <p> 085 * @param numTimeouts The maximum number of timeouts to allow. Values 086 * less than 1 should not be used, but if they are, they are 087 * treated as 1. 088 ***/ 089 public void setMaxTimeouts(int numTimeouts) 090 { 091 if (numTimeouts < 1) 092 __maxTimeouts = 1; 093 else 094 __maxTimeouts = numTimeouts; 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 sent = 143 new TFTPReadRequestPacket(host, port, filename, mode); 144 145 _sendPacket: 146 do 147 { 148 bufferedSend(sent); 149 150 _receivePacket: 151 while (true) 152 { 153 timeouts = 0; 154 while (timeouts < __maxTimeouts) 155 { 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; 169 } 170 catch (InterruptedIOException e) 171 { 172 if (++timeouts >= __maxTimeouts) 173 { 174 endBufferedOps(); 175 throw new IOException("Connection timed out."); 176 } 177 continue; 178 } 179 catch (TFTPPacketException e) 180 { 181 endBufferedOps(); 182 throw new IOException("Bad packet: " + e.getMessage()); 183 } 184 } 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 continue _receivePacket; // Start fetching packets again. 252 } 253 //break; 254 255 default: 256 endBufferedOps(); 257 throw new IOException("Received unexpected packet type."); 258 } 259 } 260 else 261 { 262 error = new TFTPErrorPacket(received.getAddress(), 263 received.getPort(), 264 TFTPErrorPacket.UNKNOWN_TID, 265 "Unexpected host or port."); 266 bufferedSend(error); 267 continue _sendPacket; 268 } 269 270 // We should never get here, but this is a safety to avoid 271 // infinite loop. If only Java had the goto statement. 272 //break; 273 } 274 275 ack.setBlockNumber(lastBlock); 276 sent = ack; 277 bytesRead += dataLength; 278 } // First data packet less than 512 bytes signals end of stream. 279 280 while (dataLength == TFTPPacket.SEGMENT_SIZE); 281 282 bufferedSend(sent); 283 endBufferedOps(); 284 285 return bytesRead; 286 } 287 288 289 /*** 290 * Requests a named file from a remote host, writes the 291 * file to an OutputStream, closes the connection, and returns the number 292 * of bytes read. A local UDP socket must first be created by 293 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 294 * invoking this method. This method will not close the OutputStream 295 * containing the file; you must close it after the method invocation. 296 * <p> 297 * @param filename The name of the file to receive. 298 * @param mode The TFTP mode of the transfer (one of the MODE constants). 299 * @param output The OutputStream to which the file should be written. 300 * @param hostname The name of the remote host serving the file. 301 * @param port The port number of the remote TFTP server. 302 * @exception IOException If an I/O error occurs. The nature of the 303 * error will be reported in the message. 304 * @exception UnknownHostException If the hostname cannot be resolved. 305 ***/ 306 public int receiveFile(String filename, int mode, OutputStream output, 307 String hostname, int port) 308 throws UnknownHostException, IOException 309 { 310 return receiveFile(filename, mode, output, InetAddress.getByName(hostname), 311 port); 312 } 313 314 315 /*** 316 * Same as calling receiveFile(filename, mode, output, host, 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 host The remote host serving the file. 322 * @exception IOException If an I/O error occurs. The nature of the 323 * error will be reported in the message. 324 ***/ 325 public int receiveFile(String filename, int mode, OutputStream output, 326 InetAddress host) 327 throws IOException 328 { 329 return receiveFile(filename, mode, output, host, DEFAULT_PORT); 330 } 331 332 /*** 333 * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT). 334 * 335 * @param filename The name of the file to receive. 336 * @param mode The TFTP mode of the transfer (one of the MODE constants). 337 * @param output The OutputStream to which the file should be written. 338 * @param hostname The name of the remote host serving the file. 339 * @exception IOException If an I/O error occurs. The nature of the 340 * error will be reported in the message. 341 * @exception UnknownHostException If the hostname cannot be resolved. 342 ***/ 343 public int receiveFile(String filename, int mode, OutputStream output, 344 String hostname) 345 throws UnknownHostException, IOException 346 { 347 return receiveFile(filename, mode, output, InetAddress.getByName(hostname), 348 DEFAULT_PORT); 349 } 350 351 352 /*** 353 * Requests to send a file to a remote host, reads the file from an 354 * InputStream, sends the file to the remote host, and closes the 355 * connection. A local UDP socket must first be created by 356 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 357 * invoking this method. This method will not close the InputStream 358 * containing the file; you must close it after the method invocation. 359 * <p> 360 * @param filename The name the remote server should use when creating 361 * the file on its file system. 362 * @param mode The TFTP mode of the transfer (one of the MODE constants). 363 * @param host The remote host receiving the file. 364 * @param port The port number of the remote TFTP server. 365 * @exception IOException If an I/O error occurs. The nature of the 366 * error will be reported in the message. 367 ***/ 368 public void sendFile(String filename, int mode, InputStream input, 369 InetAddress host, int port) throws IOException 370 { 371 int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket; 372 TFTPPacket sent, received = null; 373 TFTPErrorPacket error; 374 TFTPDataPacket data = 375 new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0); 376 TFTPAckPacket ack; 377 378 boolean justStarted = true; 379 380 beginBufferedOps(); 381 382 dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0; 383 block = 0; 384 boolean lastAckWait = false; 385 386 if (mode == TFTP.ASCII_MODE) 387 input = new ToNetASCIIInputStream(input); 388 389 sent = 390 new TFTPWriteRequestPacket(host, port, filename, mode); 391 392 _sendPacket: 393 do 394 { 395 // first time: block is 0, lastBlock is 0, send a request packet. 396 // subsequent: block is integer starting at 1, send data packet. 397 bufferedSend(sent); 398 399 // this is trying to receive an ACK 400 _receivePacket: 401 while (true) 402 { 403 404 405 timeouts = 0; 406 while (timeouts < __maxTimeouts) 407 { 408 try 409 { 410 received = bufferedReceive(); 411 break; 412 } 413 catch (SocketException e) 414 { 415 if (++timeouts >= __maxTimeouts) 416 { 417 endBufferedOps(); 418 throw new IOException("Connection timed out."); 419 } 420 continue; 421 } 422 catch (InterruptedIOException e) 423 { 424 if (++timeouts >= __maxTimeouts) 425 { 426 endBufferedOps(); 427 throw new IOException("Connection timed out."); 428 } 429 continue; 430 } 431 catch (TFTPPacketException e) 432 { 433 endBufferedOps(); 434 throw new IOException("Bad packet: " + e.getMessage()); 435 } 436 } // end of while loop over tries to receive 437 438 // The first time we receive we get the port number and 439 // answering host address (for hosts with multiple IPs) 440 if (justStarted) 441 { 442 justStarted = false; 443 hostPort = received.getPort(); 444 data.setPort(hostPort); 445 if(!host.equals(received.getAddress())) 446 { 447 host = received.getAddress(); 448 data.setAddress(host); 449 sent.setAddress(host); 450 } 451 } 452 453 // Comply with RFC 783 indication that an error acknowledgement 454 // should be sent to originator if unexpected TID or host. 455 if (host.equals(received.getAddress()) && 456 received.getPort() == hostPort) 457 { 458 459 switch (received.getType()) 460 { 461 case TFTPPacket.ERROR: 462 error = (TFTPErrorPacket)received; 463 endBufferedOps(); 464 throw new IOException("Error code " + error.getError() + 465 " received: " + error.getMessage()); 466 case TFTPPacket.ACKNOWLEDGEMENT: 467 ack = (TFTPAckPacket)received; 468 469 lastBlock = ack.getBlockNumber(); 470 471 if (lastBlock == block) 472 { 473 ++block; 474 if (block > 65535) 475 { 476 // wrap the block number 477 block = 0; 478 } 479 if (lastAckWait) { 480 481 break _sendPacket; 482 } 483 else { 484 break _receivePacket; 485 } 486 } 487 else 488 { 489 discardPackets(); 490 491 if (lastBlock == (block == 0 ? 65535 : (block - 1))) 492 continue _sendPacket; // Resend last acknowledgement. 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 }