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