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