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