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