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