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