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