summaryrefslogblamecommitdiffstats
path: root/cpukit/libfs/src/ftpfs/tftpDriver.c
blob: 59136ef59f459cfd1bc187b28a5c8bb74ceefb36 (plain) (tree)
1
2
3
4
5
6
7



                                           
  
                           
  












                                                                          


   

                                                                                
  



















                                                                              

   



                    
                  
                   
                     



                   
                  
                  
                       



                       
                  
 
                        
 


                            
                                 

  

                 
                                               














                                                       



               




                             



























                                                         

  

                                                                    
   






                                                                             




                                       



                     
                             
                                      





                     

                               
                             





                    

                               


              







                                


                      

                                
                                     
                





                            
      

                                                                       
       

                                  

      
                                                                       
       
                           

      






































                                                                            







                                   


                                                                        







                       































                                                                            


  
                                         
   













                                                
 
  
                                                       
   







                                                                              
 
  
                               
   



                               
                     
                               

 

                                               
 






                                                                   
   


















                                                       
   
 










                                                         
       


                            
   
 
                                

 






                                  
 











                                                   
 
                


  
                    
   
                                          
 









                                   
                                                                  

      
                                   



                                                           


  
                                    
   




























































                                                                          
 

            

                                
                                       





                                                     





                                                                                




                                                                                 


  








                                                                      
   
              
                                                 
 
                
                      











                                                                         
     




                                 
                                        






                                   






                                                                       
                                                           








                                                  



                                                                         
     
               

 





























                                                                         
  













                                                                          
   


                                                    
 
               
      



                                                                              
       



                                                              
 
                                                    
      
                                                                              
       
                                         

 
                                                                   
 


                               
 




























































                                                                                
     

                                                                    
 
 
                                                                  
 









                                                                               
     







                                                                          

 





                                                

 


                                                          
 




                                                                             
 
      




                                                                       
       



                                                                             
            

                                           
     
 




                                                         
      
                                                         
       

                                                 
     
 















                                                                            
     
                                                                        
 
      
                                 
       
























                                                                   
     
                             
          

                                                           
           













































































































































                                                                              
 


































































                                                                                
         
     







































                                                                              

      
                        
       
                                                             

                           


      
                                      
       



                                               


      






                                                                      

                                                 
       
























                                                                                
 







                                                                         
      
                            
       




                                          
 








                                                                   
 






                                                          
 










                                                     
         













                                                              
     




                                                              

 





                                             

 


                                    
 














                                                                

     
      
                                  
       






                                                              
 








                                                                        
 


                                                   


             


  

                          

                               
                          
                        

 
                          
                                        
                            
                          
 

                                                            
 
      
                                                            
       








                                  
                                                                           





                               
         
                         
                  
         



                                   















                                                                  
         
     




                                                    








                                                                         
   
                                                   
 
            
 





                                                
          





                                                                  
           


                              
         










                                                                              
 
             




                      

                     

 
                                        
                             
 


                     
 


                                                          
                                  


                                         



                                      






                                                      
                                                            

                                                        

                       

 

                                 
                            
                          

 
                          
                                        
                                          

                               



                                        


                                                                           










                                                                       
                                           



                          

                                                                             


                           




                                          
             
                          


                 

 

                     

 


                                        
     
 


                           
 





                                          
 
/* SPDX-License-Identifier: BSD-2-Clause */

/**
 * @file
 *
 * @ingroup RTEMSImplTFTPFS
 *
 * @brief This source file contains the implementation of
 *   a Trivial File Transfer Protocol (TFTP) client library.
 *
 * The code in this file provides the ability to read files from and
 * to write files to remote servers using the Trivial File Transfer
 * Protocol (TFTP). It is used by the @ref tftpfs.c "TFTP file system" and
 * tested through its test suite. The
 * following RFCs are implemented:
 *
 *   + RFC 1350 "The TFTP Protocol (Revision 2)"
 *   + RFC 2347 "TFTP Option Extension"
 *   + RFC 2348 "TFTP Blocksize Option"
 *   + RFC 7440 "TFTP Windowsize Option"
 */

/*
 * Copyright (C) 1998 W. Eric Norum <eric@norum.ca>
 * Copyright (C) 2012, 2022 embedded brains GmbH (http://www.embedded-brains.de)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <rtems.h>
#include <rtems/tftp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "tftp_driver.h"

/*
 * Range of UDP ports to try
 */
#define UDP_PORT_BASE        3180

/*
 * Default limits
 */
#define PACKET_FIRST_TIMEOUT_MILLISECONDS  400L
#define TFTP_WINDOW_SIZE_MIN               1
#define TFTP_BLOCK_SIZE_MIN                8
#define TFTP_BLOCK_SIZE_MAX                65464

#define TFTP_BLOCK_SIZE_OPTION             "blksize"
#define TFTP_WINDOW_SIZE_OPTION            "windowsize"
#define TFTP_DECIMAL_BASE                  10

#define TFTP_DEFAULT_SERVER_PORT           69

/*
 * These values are suggested by RFC 7440.
 */
#define TFTP_RFC7440_DATA_RETRANSMISSIONS  6
#define TFTP_RFC7440_TIMEOUT_MILLISECONDS  1000

/*
 * TFTP opcodes
 */
#define TFTP_OPCODE_RRQ     1
#define TFTP_OPCODE_WRQ     2
#define TFTP_OPCODE_DATA    3
#define TFTP_OPCODE_ACK     4
#define TFTP_OPCODE_ERROR   5
#define TFTP_OPCODE_OACK    6

/*
 * TFTP error codes
 */
#define TFTP_ERROR_CODE_NOT_DEFINED 0
#define TFTP_ERROR_CODE_NOT_FOUND   1
#define TFTP_ERROR_CODE_NO_ACCESS   2
#define TFTP_ERROR_CODE_DISK_FULL   3
#define TFTP_ERROR_CODE_ILLEGAL     4
#define TFTP_ERROR_CODE_UNKNOWN_ID  5
#define TFTP_ERROR_CODE_FILE_EXISTS 6
#define TFTP_ERROR_CODE_NO_USER     7
#define TFTP_ERROR_CODE_OPTION_NEGO 8

/*
 * Special return values for process_*_packet() functions
 * (other return values are POSIX errors)
 */
#define GOT_EXPECTED_PACKET 0
#define GOT_DUPLICATE_OF_CURRENT_PACKET -1
#define GOT_OLD_PACKET -2
#define GOT_FIRST_OUT_OF_ORDER_PACKET -3

/*
 * Special argument value for getPacket()
 */
#define GET_PACKET_DONT_WAIT -1

/*
 * Special return value for prepare_*_packet_for_sending() functions
 * (other return values are the length of the packet to be send)
 */
#define DO_NOT_SEND_PACKET 0

#define PKT_SIZE_FROM_BLK_SIZE(_blksize) ((_blksize) + 2 * sizeof (uint16_t))
#define BLK_SIZE_FROM_PKT_SIZE(_pktsize) ((_pktsize) - 2 * sizeof (uint16_t))
#define MUST_SEND_OPTIONS(_options) (\
    (_options).block_size  != TFTP_RFC1350_BLOCK_SIZE || \
    (_options).window_size != TFTP_RFC1350_WINDOW_SIZE )

/*
 * Packets transferred between machines
 */
union tftpPacket {
    /*
     * RRQ/WRQ packet
     */
    struct tftpRWRQ {
        uint16_t      opcode;
        char          filename_mode[];
    } tftpRWRQ;

    /*
     * DATA packet
     */
    struct tftpDATA {
        uint16_t      opcode;
        uint16_t      blocknum;
        uint8_t       data[];
    } tftpDATA;

    /*
     * ACK packet
     */
    struct tftpACK {
        uint16_t      opcode;
        uint16_t      blocknum;
    } tftpACK;

    /*
     * OACK packet
     */
    struct tftpOACK {
        uint16_t      opcode;
        char          options[];
    } tftpOACK;

    /*
     * ERROR packet
     */
    struct tftpERROR {
        uint16_t      opcode;
        uint16_t      errorCode;
        char          errorMessage[];
    } tftpERROR;
};

/*
 * State of each TFTP stream
 */
struct tftpStream {
    /*
     * Buffer for storing packets for sending and receiving.  Can point
     * to the same address when only one buffer is needed for reading.
     */
    union tftpPacket *receive_buf;
    union tftpPacket *send_buf;

    /*
     * Current block number - i.e. the block currently send or received
     */
    uint16_t      blocknum;

    /*
     * Size of the data area in a DATA single packet.
     */
    size_t block_size;

    /*
     * The maximum size of a packet.  It depends linearly on the block_size.
     * The receive_buf and (the packets in) the send_buf are of this size.
     */
    size_t packet_size;

    /*
     * The number of packets which can be stored in the send buffer.
     * During option negotiation and for reading a file from the server
     * only a buffer for a single packet is needed.  In those cases, this
     * value is always one.  When a file is written to the server,
     * the value equals the window size:
     *     send_buf_size_in_pkts == server_options.window_size
     *
     * Packet N is stored in
     *     send_buf + packet_size * (N % send_buf_size_in_pkts)
     */
    uint16_t send_buf_size_in_pkts;

    /*
     * When writing files with windowsize > 1, the number of the completely
     * filled packet with the highest block number in the send buffer.
     * When the user calls write(), the data will be written into
     * the block after this one.
     */
    uint16_t blocknum_last_filled;

    /*
     * When writing files with windowsize > 1, the number of the packet
     * which is the last one in the whole file (i.e. the user
     * called close()).  This block is never full (but maybe empty).
     */
    uint16_t blocknum_eof_block;

    /*
     * Data transfer socket
     */
    int                 socket;
    struct sockaddr_in  myAddress;
    struct sockaddr_in  farAddress;

    /*
     * Indices into buffer
     * In case of sending a file with windowsize > 1, these values apply
     * only to the packet with the highest number in the send buffer
     * (blocknum_last_filled + 1).
     */
    int     nleft;
    int     nused;

    /*
     * Flags
     */
    int     firstReply;
    bool    at_eof;
    bool    is_for_reading;

    /*
     * Function pointers and members for use by communicate_with_server().
     */
    ssize_t (*prepare_packet_for_sending) (
        struct tftpStream *tp,
        bool force_retransmission,
        union tftpPacket **send_buf,
        bool *wait_for_packet_reception,
        const void *create_packet_data
    );
    int (*process_data_packet) (struct tftpStream *tp, ssize_t len);
    int (*process_ack_packet) (struct tftpStream *tp, ssize_t len);
    int (*process_oack_packet) (struct tftpStream *tp, ssize_t len);
    int (*process_error_packet) (struct tftpStream *tp, ssize_t len);
    int retransmission_error_code;
    bool ignore_out_of_order_packets;
    int32_t blocknum_of_first_packet_of_window;
    int error;

    /*
     * Configuration and TFTP options
     *
     *     * config.options are options desired by the user (i.e. the values
     *       send to the server).
     *     * server_options are the options agreed by the server
     *       (the option values actually used for the transfer of data).
     */
    tftp_net_config config;
    tftp_options server_options;
};

/*
 * Forward declaration cannot be avoided.
 */
static ssize_t prepare_data_packet_for_sending (
    struct tftpStream *tp,
    bool force_retransmission,
    union tftpPacket **send_buf,
    bool *wait_for_packet_reception,
    const void *path_name
);
static ssize_t prepare_ack_packet_for_sending (
    struct tftpStream *tp,
    bool force_retransmission,
    union tftpPacket **send_buf,
    bool *wait_for_packet_reception,
    const void *path_name
);

/*
 * Calculate the address of packet N in the send buffer
 */
static union tftpPacket *get_send_buffer_packet (
    struct tftpStream *tp,
    uint16_t packet_num
)
{
    return (union tftpPacket *) ( ( (char *) tp->send_buf) + tp->packet_size *
        (packet_num % tp->send_buf_size_in_pkts) );
}

/*
 * Create read or write request
 */
static size_t create_request (
    union tftpPacket *send_buf,
    size_t data_size,
    bool is_for_reading,
    const char *path,
    const tftp_options *options
)
{
  size_t res_size;
  char *cur = send_buf->tftpRWRQ.filename_mode;

  send_buf->tftpRWRQ.opcode = htons (
      is_for_reading ? TFTP_OPCODE_RRQ : TFTP_OPCODE_WRQ
  );

  res_size = snprintf (cur, data_size, "%s%c%s", path, 0, "octet");
  if (res_size >= data_size) {
     return -1;
  }
  res_size++;
  data_size -= res_size;
  cur += res_size;

  if (options->block_size != TFTP_RFC1350_BLOCK_SIZE) {
      res_size = snprintf (
          cur,
          data_size,
          "%s%c%"PRIu16,
          TFTP_BLOCK_SIZE_OPTION,
          0,
          options->block_size
      );
      if (res_size >= data_size) {
          return -1;
      }
      res_size++;
      data_size -= res_size;
      cur += res_size;
  }

  if (options->window_size != TFTP_RFC1350_WINDOW_SIZE) {
      res_size = snprintf (
          cur,
          data_size,
          "%s%c%"PRIu16,
          TFTP_WINDOW_SIZE_OPTION,
          0,
          options->window_size
      );
      if (res_size >= data_size) {
          return -1;
      }
      res_size++;
      data_size -= res_size;
      cur += res_size;
  }

  return cur - (char *)send_buf;
}

static bool parse_decimal_number (
    char **pos,
    size_t *remain,
    long min,
    long max,
    uint16_t *variable
)
{
    long value;
    const char *start = *pos;
    if (*remain < 2) {
        return false;
    }
    value = strtoul(start, pos, TFTP_DECIMAL_BASE);
    if (value < min || value > max || **pos != 0) {
        return false;
    }
    *variable = (uint16_t) value;
    (*pos)++;
    *remain -= *pos - start;

    return true;
}

/*
 * Map error message
 */
static int tftpErrno (uint16_t error_code)
{
    unsigned int tftpError;
    static const int errorMap[] = {
        EINVAL,
        ENOENT,
        EPERM,
        ENOSPC,
        EINVAL,
        ENXIO,
        EEXIST,
        ESRCH,
        ENOTSUP, /* Error: Option negotiation failed (RFC 2347) */
    };

    tftpError = ntohs (error_code);
    if (tftpError < (sizeof errorMap / sizeof errorMap[0]))
        return errorMap[tftpError];
    else
        return 1000 + tftpError;
}

/*
 * Parse options from an OACK packet
 */
static bool parse_options (
    union tftpPacket *receive_buf,
    size_t packet_size,
    tftp_options *options_in,
    tftp_options *options_out
)
{
    char *pos = receive_buf->tftpOACK.options;
    size_t remain   = packet_size - sizeof (receive_buf->tftpOACK.opcode);

    /*
     * Make sure there is a 0 byte in the end before comparing strings
     */
    if (remain > 0 && pos[remain - 1] != 0) {
        return false;
    }

    while (remain > 0) {
      if (strcasecmp(pos, TFTP_BLOCK_SIZE_OPTION) == 0 &&
          options_in->block_size != TFTP_RFC1350_BLOCK_SIZE) {
          remain -= sizeof (TFTP_BLOCK_SIZE_OPTION);
          pos    += sizeof (TFTP_BLOCK_SIZE_OPTION);
          if (!parse_decimal_number (
              &pos,
              &remain,
              TFTP_BLOCK_SIZE_MIN,
              options_in->block_size,
              &options_out->block_size)) {
              return false;
          };

      } else if (strcasecmp(pos, TFTP_WINDOW_SIZE_OPTION) == 0 &&
          options_in->window_size != TFTP_RFC1350_WINDOW_SIZE) {
          remain -= sizeof (TFTP_WINDOW_SIZE_OPTION);
          pos    += sizeof (TFTP_WINDOW_SIZE_OPTION);
          if (!parse_decimal_number (
              &pos,
              &remain,
              TFTP_WINDOW_SIZE_MIN,
              options_in->window_size,
              &options_out->window_size)) {
              return false;
          };

      } else {
         return false; /* Unknown option */
      }
    }

    return true;
}

/*
 * Send an error message
 */
static void send_error (
    struct tftpStream *tp,
    struct sockaddr_in *to,
    uint8_t error_code,
    const char *error_message
)
{
    int len;
    struct {
        uint16_t      opcode;
        uint16_t      errorCode;
        char          errorMessage[80];
    } msg;

    /*
     * Create the error packet (Unknown transfer ID).
     */
    msg.opcode = htons (TFTP_OPCODE_ERROR);
    msg.errorCode = htons (error_code);
    len = snprintf (msg.errorMessage, sizeof (msg.errorMessage), error_message);
    if (len >= sizeof (msg.errorMessage)) {
      len = sizeof (msg.errorMessage) - 1;
    }
    len += sizeof (msg.opcode) + sizeof (msg.errorCode) + 1;

    /*
     * Send it
     */
    sendto (tp->socket, (char *)&msg, len, 0, (struct sockaddr *)to, sizeof *to);
}

/*
 * Send a message to make the other end shut up
 */
static void sendStifle (struct tftpStream *tp, struct sockaddr_in *to)
{
    send_error (tp, to, TFTP_ERROR_CODE_UNKNOWN_ID, "GO AWAY");
}

/*
 * Wait for a packet
 */
static ssize_t
getPacket (struct tftpStream *tp, int retryCount)
{
    ssize_t len;
    struct timeval tv;
    int flags = 0;

    if (retryCount == GET_PACKET_DONT_WAIT) {
        flags = MSG_DONTWAIT;
    } else if (retryCount == 0) {
        tv.tv_sec = tp->config.first_timeout / 1000L;
        tv.tv_usec = (tp->config.first_timeout % 1000L) * 1000L;
        setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
    } else {
        tv.tv_sec = tp->config.timeout / 1000L;
        tv.tv_usec = (tp->config.timeout % 1000L) * 1000L;
        setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
    }
    for (;;) {
        union {
            struct sockaddr s;
            struct sockaddr_in i;
        } from;
        socklen_t fromlen = sizeof from;
        len = recvfrom (tp->socket,
            tp->receive_buf,
            tp->packet_size,
            flags,
            &from.s,
            &fromlen
        );
        if (len < 0)
            break;
        if (from.i.sin_addr.s_addr == tp->farAddress.sin_addr.s_addr) {
            if (tp->firstReply) {
                tp->firstReply = 0;
                tp->farAddress.sin_port = from.i.sin_port;
            }
            if (tp->farAddress.sin_port == from.i.sin_port)
                break;
        }

        /*
         * Packet is from someone with whom we are
         * not interested.  Tell them to go away.
         */
        sendStifle (tp, &from.i);
    }
    if (retryCount != GET_PACKET_DONT_WAIT) {
        tv.tv_sec = 0;
        tv.tv_usec = 0;
        setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
    }
    return len;
}

static int process_unexpected_packet (struct tftpStream *tp, ssize_t len)
{
    (void) len;
    send_error (
        tp,
        &tp->farAddress,
        TFTP_ERROR_CODE_ILLEGAL,
        "Got packet with unexpected opcode from server"
    );
    return EPROTO;
}

static int process_malformed_packet (struct tftpStream *tp, ssize_t len)
{
    (void) len;
    send_error (
        tp,
        &tp->farAddress,
        TFTP_ERROR_CODE_ILLEGAL,
        "Got malformed packet from server"
    );
    return EPROTO;
}

static int process_error_packet (struct tftpStream *tp, ssize_t len)
{
    (void) len;
    return tftpErrno (tp->receive_buf->tftpERROR.errorCode);
}

/*
 * When an RRQ or a WRQ with options is sent and the server responds with
 * an error, this function will trigger a re-sent of an RRQ or WRQ
 * without options (falling back to old RFC1350).
 *
 * If someone wants to change the implementation to force using options
 * (i.e. prevent fallback to RFC1350), at least these points must be
 * considered:
 *
 *     * Use `process_error_packet()` instead of
 *       `process_error_packet_option_negotiation()`
 *     * React to DATA and ACK packets, which are an immediate response to
 *       an RRQ or a WRQ with options, with an error packet.
 *     * Check the option values in the OACK whether they are in the
 *       desired range.
 */
static int process_error_packet_option_negotiation (
    struct tftpStream *tp, ssize_t len
)
{
    (void) len;
    /*
     * Setting tp->config.options causes an RRQ or a WRQ to be created without
     * options.
     * Setting tp->server_option is defensive programming as these fields
     * should already have these values.
     */
    tp->config.options.block_size  = TFTP_RFC1350_BLOCK_SIZE;
    tp->config.options.window_size = TFTP_RFC1350_WINDOW_SIZE;
    tp->server_options.block_size  = TFTP_RFC1350_BLOCK_SIZE;
    tp->server_options.window_size = TFTP_RFC1350_WINDOW_SIZE;

    tp->process_error_packet = process_error_packet;
    /*
     * GOT_FIRST_OUT_OF_ORDER_PACKET will trigger a re-send of the RRQ or WRQ.
     */
    return GOT_FIRST_OUT_OF_ORDER_PACKET;
}

static int process_data_packet (struct tftpStream *tp, ssize_t len)
{
    ssize_t plen;
    int32_t pkt_blocknum;
    union tftpPacket *send_buf;

    if (len < sizeof (tp->receive_buf->tftpACK)) {
        return process_malformed_packet (tp, len);
    }
    pkt_blocknum = (int32_t) ntohs (tp->receive_buf->tftpACK.blocknum);
    if (pkt_blocknum == 0) {
        return process_malformed_packet (tp, len);
    }

    /*
     * In case of reading a file from the server:
     * If the latest ACK packet(s) did not reach the server, the server
     * starts the window from the last ACK it received.  This if-clause
     * ensures, the client sends an ACK after having seen `windowsize`
     * packets.
     */
    if (pkt_blocknum < tp->blocknum_of_first_packet_of_window &&
        pkt_blocknum >= (int32_t) tp->blocknum + 1 -
            (int32_t) tp->server_options.window_size) {
        tp->blocknum_of_first_packet_of_window = pkt_blocknum;
    }
    if (!tp->ignore_out_of_order_packets &&
       pkt_blocknum > (int32_t) tp->blocknum + 1) {
        tp->ignore_out_of_order_packets = true;
        return GOT_FIRST_OUT_OF_ORDER_PACKET;
    } else if (pkt_blocknum == (int32_t) tp->blocknum) {
      /*
       * In case of reading a file from the server:
       * If the last ACK packet send by the client did not reach the
       * server, the server re-sends all packets of the window.  In this
       * case, the client must re-send the ACK packet after having
       * received the last packet of the window (even through it has
       * already received that packet before).
       * GOT_OLD_PACKET would wrongly suppress this.
       */
      return GOT_DUPLICATE_OF_CURRENT_PACKET;
    } else if (pkt_blocknum != (int32_t) tp->blocknum + 1) {
        return GOT_OLD_PACKET;
    }
    tp->ignore_out_of_order_packets = false;

    tp->blocknum++;
    tp->nused = 0; /* Only for 2nd, 3rd, 4th DATA packet received */
    tp->nleft = BLK_SIZE_FROM_PKT_SIZE (len);
    tp->at_eof = (tp->nleft < tp->server_options.block_size);
    /*
     * After the last DATA packet, the client must send a final ACK
     */
    if (tp->at_eof) {
        plen = prepare_ack_packet_for_sending (tp, true, &send_buf, NULL, NULL);

        /*
         * Send it. Errors during send will not matter for this last ACK.
         */
        sendto (
            tp->socket,
            send_buf,
            plen,
            0,
            (struct sockaddr *) &tp->farAddress,
            sizeof (tp->farAddress)
        );
    }
    tp->prepare_packet_for_sending = prepare_ack_packet_for_sending;
    return GOT_EXPECTED_PACKET;
}

static int process_ack_packet (struct tftpStream *tp, ssize_t len)
{
    uint16_t blocknum_ack;
    if (len < sizeof (tp->receive_buf->tftpACK)) {
        return process_malformed_packet (tp, len);
    }
    blocknum_ack = ntohs (tp->receive_buf->tftpACK.blocknum);
    if ((int32_t) blocknum_ack == tp->blocknum_of_first_packet_of_window - 1 &&
        blocknum_ack != 0
    ) {
        tp->blocknum = tp->blocknum_of_first_packet_of_window;
        return GOT_DUPLICATE_OF_CURRENT_PACKET;
    }
    if ((int32_t) blocknum_ack < tp->blocknum_of_first_packet_of_window ||
        blocknum_ack > tp->blocknum_last_filled) {
        return GOT_OLD_PACKET;
    }
    tp->blocknum = blocknum_ack + 1;
    tp->blocknum_of_first_packet_of_window = (int32_t) tp->blocknum;
    tp->prepare_packet_for_sending = prepare_data_packet_for_sending;
    return GOT_EXPECTED_PACKET;
}

static ssize_t prepare_data_packet_for_sending (
    struct tftpStream *tp,
    bool force_retransmission,
    union tftpPacket **send_buf,
    bool *wait_for_packet_reception,
    const void *not_used
)
{
    (void) not_used;
    ssize_t len;
    *send_buf = get_send_buffer_packet (tp, tp->blocknum);

    len = PKT_SIZE_FROM_BLK_SIZE (
        (tp->blocknum == tp->blocknum_eof_block) ? tp->nused : tp->block_size
    );
    (*send_buf)->tftpDATA.opcode   = htons (TFTP_OPCODE_DATA);
    (*send_buf)->tftpDATA.blocknum = htons (tp->blocknum);

    /*
     * If the client sends the last packet of a window,
     * it must wait for an ACK and - in case no ACK is received - begin
     * a retransmission with the first packet of the window.
     * Note that the last DATA block for the whole transfer is also
     * a "last packet of a window".
     */
    if ((int32_t) tp->blocknum + 1 >=
        tp->blocknum_of_first_packet_of_window + tp->send_buf_size_in_pkts ||
        tp->blocknum == tp->blocknum_eof_block) {
        tp->blocknum = (uint16_t) tp->blocknum_of_first_packet_of_window;
    } else {
        tp->blocknum++;
        *wait_for_packet_reception = false;
    }

    tp->process_data_packet  = process_unexpected_packet;
    tp->process_ack_packet   = process_ack_packet;
    tp->process_oack_packet  = process_unexpected_packet;
    tp->process_error_packet = process_error_packet;

    /*
     * Our last packet won't necessarily be acknowledged!
     */
    if (tp->blocknum == tp->blocknum_eof_block) {
        tp->retransmission_error_code = 0;
    }

    return len;
}

static ssize_t prepare_ack_packet_for_sending (
    struct tftpStream *tp,
    bool force_retransmission,
    union tftpPacket **send_buf,
    bool *wait_for_packet_reception,
    const void *path_name
)
{
    (void) wait_for_packet_reception;
    if (!force_retransmission &&
        tp->blocknum_of_first_packet_of_window - 1 +
        (int32_t) tp->server_options.window_size > (int32_t) tp->blocknum) {
      return DO_NOT_SEND_PACKET;
    }
    tp->blocknum_of_first_packet_of_window = (int32_t) tp->blocknum + 1;

    /*
     * Create the acknowledgement
     */
    *send_buf = tp->send_buf;
    (*send_buf)->tftpACK.opcode   = htons (TFTP_OPCODE_ACK);
    (*send_buf)->tftpACK.blocknum = htons (tp->blocknum);

    tp->process_data_packet  = process_data_packet;
    tp->process_ack_packet   = process_unexpected_packet;
    tp->process_oack_packet  = process_unexpected_packet;
    tp->process_error_packet = process_error_packet;

    return sizeof (tp->send_buf->tftpACK);
}

static int process_oack_packet (struct tftpStream *tp, ssize_t len)
{
    if (!parse_options(tp->receive_buf,
        len,
        &tp->config.options,
        &tp->server_options)) {
        send_error (
            tp,
            &tp->farAddress,
            TFTP_ERROR_CODE_OPTION_NEGO,
            "Bad options, option values or malformed OACK packet"
        );
        return EPROTO;
    }
    if (tp->is_for_reading) {
        /*
         * Since no DATA packet has been received yet, tell
         * tftp_read() there is no data left.
         */
        tp->nleft = 0;
        tp->prepare_packet_for_sending = prepare_ack_packet_for_sending;
    } else {
        tp->blocknum_of_first_packet_of_window = 1;
        tp->blocknum = (uint16_t) tp->blocknum_of_first_packet_of_window;
        tp->prepare_packet_for_sending = prepare_data_packet_for_sending;
    }
    return GOT_EXPECTED_PACKET;
}

static ssize_t prepare_request_packet_for_sending (
    struct tftpStream *tp,
    bool force_retransmission,
    union tftpPacket **send_buf,
    bool *wait_for_packet_reception,
    const void *path_name
)
{
    (void) wait_for_packet_reception;
    ssize_t len;
    *send_buf = tp->send_buf;
    len = create_request (
        *send_buf,
        tp->block_size,
        tp->is_for_reading,
        path_name,
        &tp->config.options
    );

    if (len < 0) {
        tp->error = ENAMETOOLONG;
    } else {
        tp->process_data_packet  = tp->is_for_reading ?
            process_data_packet : process_unexpected_packet;
        tp->process_ack_packet   = tp->is_for_reading ?
            process_unexpected_packet : process_ack_packet;
        tp->process_oack_packet  = MUST_SEND_OPTIONS(tp->config.options) ?
            process_oack_packet : process_unexpected_packet;
        tp->process_error_packet = MUST_SEND_OPTIONS(tp->config.options) ?
            process_error_packet_option_negotiation : process_error_packet;
    }

    /*
     * getPacket() will change these values when the first packet is
     * received.  Yet, this first packet may be an unexpected one
     * (e.g. an ERROR or having a wrong block number).
     * If a second, third, forth, ... RRQ/WRQ is to be sent, it should
     * be directed to the server port again and not to the port the
     * first unexpected packet came from.
     */
    tp->farAddress.sin_port = htons (tp->config.server_port);
    tp->firstReply          = 1;

    return len;
}

/*
 * Conduct one communication step with the server. For windowsize == 1,
 * one step is:
 *   a) Send a packet to the server
 *   b) Receive a reply packet from the server
 *   c) Handle errors (if any)
 *   d) If no packet has been received from the server and the maximum
 *      retransmission count has not yet been reached, start over with a)
 * The flow of packets (i.e. which packet to send and which packet(s) to
 * expect from the server) is controlled by function pointers found in
 * struct tftpStream.
 *
 * Besides of handling errors and retransmissions, the essential data exchange
 * follows these patterns:
 *
 * Connection establishment and option negotiation:
 *   * Send RRQ/WRQ (read or write request packet)
 *   * Receive OACK (read and write) or ACK (write only) or DATA (read only)
 *
 * Read step with windowsize == 1:
 *   * Send ACK packet
 *   * Receive DATA packet
 * Sending the very last ACK packet for a "read" session is treated as a
 * special case.
 *
 * Write step with windowsize == 1:
 *   * Send DATA packet
 *   * Receive ACK packet
 *
 * A windowsize lager than one makes thinks more complicated.
 * In this case, a step normally only receives (read) or only sends (write)
 * a packet.  The sending or receiving of the ACK packets is skipped normally
 * and happens only at the last step of the window (in which case this last
 * step is similar to the windowsize == 1 case):
 *
 * Normal read step with windowsize > 1:
 *   * Receive DATA packet
 * Last read step of a window with windowsize > 1:
 *   * Send ACK packet
 *   * Receive DATA packet
 *
 * Normal write step with windowsize > 1:
 *   * Send DATA packet
 *   * Check for an ACK packet but do not wait for it
 * Last write step of a window with windowsize > 1:
 *   * Send DATA packet
 *   * Receive ACK packet
 *
 * The "normal write step for windowsize > 1" checks whether an ACK packet
 * has been received after each sending of a DATA packet.  Package lost and
 * exchanges in the network can give rise to situations in which the server
 * sends more than a single ACK packet during a window.  If these packets
 * are not reacted on immediately, the network would be flooded with
 * surplus packets.  (An example where two ACK packets appear in a window
 * appears in test case "read_file_windowsize_trouble" where the client/server
 * roles are exchanged.)
 */
static int communicate_with_server (
    struct tftpStream    *tp,
    const void *create_packet_data
)
{
    ssize_t len;
    uint16_t opcode;
    union tftpPacket *send_buf;
    bool received_duplicated_or_old_package = false;
    bool force_retransmission = false;
    bool wait_for_packet_reception;
    int retryCount = 0;
    while (tp->error == 0) {

        if (!received_duplicated_or_old_package) {
            wait_for_packet_reception = true;
            len = tp->prepare_packet_for_sending (
                tp,
                force_retransmission,
                &send_buf,
                &wait_for_packet_reception,
                create_packet_data
                );
            if (len < 0) {
                if (tp->error == 0) {
                    tp->error = EIO;
                }
                break;
            }

            if (len != DO_NOT_SEND_PACKET) {
                /*
                 * Send the packet
                 */
                if (sendto (tp->socket, send_buf, len, 0,
                            (struct sockaddr *)&tp->farAddress,
                            sizeof tp->farAddress) < 0) {
                    tp->error = EIO;
                    break;
                }
            }
        }
        received_duplicated_or_old_package = false;
        force_retransmission = false;

        /*
         * Get reply
         */
        len = getPacket (
            tp,
            wait_for_packet_reception ? retryCount : GET_PACKET_DONT_WAIT
        );
        if (len >= (int) sizeof (tp->receive_buf->tftpDATA.opcode)) {
            opcode = ntohs (tp->receive_buf->tftpDATA.opcode);
            switch (opcode) {
            case TFTP_OPCODE_DATA:
                tp->error = tp->process_data_packet (tp, len);
                break;
            case TFTP_OPCODE_ACK:
                tp->error = tp->process_ack_packet (tp, len);
                break;
            case TFTP_OPCODE_OACK:
                tp->error = tp->process_oack_packet (tp, len);
                break;
            case TFTP_OPCODE_ERROR:
                tp->error = tp->process_error_packet (tp, len);
                break;
            default:
                tp->error = process_unexpected_packet (tp, len);
                break;
            }
            if (tp->error == GOT_EXPECTED_PACKET) {
                break;
            } else if (tp->error == GOT_DUPLICATE_OF_CURRENT_PACKET) {
                tp->error = 0;
            } else if (tp->error == GOT_OLD_PACKET) {
                received_duplicated_or_old_package = true;
                tp->error = 0;
            } else if (tp->error <= GOT_FIRST_OUT_OF_ORDER_PACKET) {
                force_retransmission = true;
                tp->error = 0;
            } /* else ... tp->error > 0 means "exit this function with error" */
        } else if (len >= 0) {
            tp->error = process_malformed_packet (tp, len);
        } else if (len < 0 && !wait_for_packet_reception) {
            tp->error = 0;
            break;
        } else {
            /*
             * Timeout or other problems to receive packets
             * Attempt a retransmission
             */
            if (++retryCount >= (int) tp->config.retransmissions) {
                tp->error = tp->retransmission_error_code;
                break;
            }
            force_retransmission = true;
        }
    }

    return tp->error;
}

/*
 * Allocate and initialize an struct tftpStream object.
 *
 * This function does not check whether the config values are in valid ranges.
 */
static struct tftpStream *create_stream(
    const tftp_net_config *config,
    const struct in_addr *farAddress,
    bool is_for_reading
)
{
    struct tftpStream *tp = NULL;
    tp = malloc (sizeof (struct tftpStream));
    if (tp == NULL) {
        return NULL;
    }

    /*
     * Initialize fields accessed by _Tftp_Destroy().
     */
    tp->receive_buf = NULL;
    tp->send_buf    = NULL;
    tp->socket      = 0;

    /*
     * Allocate send and receive buffer for exchange of RRQ/WRQ and ACK/OACK.
     */
    tp->block_size  = TFTP_RFC1350_BLOCK_SIZE;
    tp->packet_size = PKT_SIZE_FROM_BLK_SIZE (tp->block_size);
    tp->receive_buf = malloc (tp->packet_size);
    if (tp->receive_buf == NULL) {
      _Tftp_Destroy (tp);
      return NULL;
    }
    tp->send_buf = tp->receive_buf;
    tp->send_buf_size_in_pkts = 1;

    /*
     * Create the socket
     */
    if ((tp->socket = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
        _Tftp_Destroy (tp);
        return NULL;
    }

    /*
     * Setup configuration and options
     */
    if ( config == NULL ) {
      tftp_initialize_net_config (&tp->config);
    } else {
      tp->config = *config;
    }

    /*
     * If the server does not confirm our option values later on,
     * use numbers from the original RFC 1350 for the actual transfer.
     */
    tp->server_options.block_size  = TFTP_RFC1350_BLOCK_SIZE;
    tp->server_options.window_size = TFTP_RFC1350_WINDOW_SIZE;

    /*
     * Set the UDP destination to the TFTP server
     * port on the remote machine.
     */
    tp->farAddress.sin_family              = AF_INET;
    tp->farAddress.sin_addr                = *farAddress;
    tp->farAddress.sin_port                = htons (tp->config.server_port);

    tp->nleft                              = 0;
    tp->nused                              = 0;
    tp->blocknum                           = 0;
    tp->blocknum_last_filled               = 0;
    tp->blocknum_eof_block                 = UINT16_MAX;
    tp->firstReply                         = 1;
    tp->at_eof                             = false;
    tp->is_for_reading                     = is_for_reading;

    tp->prepare_packet_for_sending         = prepare_request_packet_for_sending;
    tp->process_data_packet                = process_unexpected_packet;
    tp->process_ack_packet                 = process_unexpected_packet;
    tp->process_oack_packet                = process_unexpected_packet;
    tp->process_error_packet               = process_error_packet;
    tp->retransmission_error_code          = EIO;
    tp->ignore_out_of_order_packets        = false;
    tp->blocknum_of_first_packet_of_window = INT32_MIN;
    tp->error                              = 0;

    return tp;
}

/*
 * Change the size of the receive and send buffer to match the options
 * values acknowledged by the server.
 */
static struct tftpStream *reallocate_stream_buffer(struct tftpStream *tp)
{
    tp->block_size = tp->server_options.block_size;
    tp->packet_size = PKT_SIZE_FROM_BLK_SIZE (tp->block_size);
    /*
     * Defensive programming
     */
    if (tp->receive_buf == tp->send_buf) {
        tp->send_buf = NULL;
    } else {
       free (tp->send_buf);
    }

    tp->receive_buf = realloc (tp->receive_buf, tp->packet_size);
    if (tp->is_for_reading) {
        tp->send_buf = tp->receive_buf;
    } else {
        tp->send_buf_size_in_pkts = tp->server_options.window_size;
        tp->send_buf = malloc (
            tp->send_buf_size_in_pkts * tp->packet_size
        );
    }

    if (tp->receive_buf == NULL || tp->send_buf == NULL) {
      sendStifle (tp,  &tp->farAddress);
      _Tftp_Destroy (tp);
      return NULL;
    }
    return tp;
}

/*
 * Convert hostname to an Internet address
 */
static struct in_addr *get_ip_address(
    const char     *hostname,
    struct in_addr *farAddress
)
{
        struct hostent *he = gethostbyname(hostname);
        if (he == NULL) {
            return NULL;
        }
        memcpy (farAddress, he->h_addr, sizeof (*farAddress));
  return farAddress;
}

void tftp_initialize_net_config (tftp_net_config *config)
{
  static const tftp_net_config default_config = {
    .retransmissions = TFTP_RFC7440_DATA_RETRANSMISSIONS,
    .server_port     = TFTP_DEFAULT_SERVER_PORT,
    .timeout         = TFTP_RFC7440_TIMEOUT_MILLISECONDS,
    .first_timeout   = PACKET_FIRST_TIMEOUT_MILLISECONDS,
    .options = {
      .block_size    = TFTP_DEFAULT_BLOCK_SIZE,
      .window_size   = TFTP_DEFAULT_WINDOW_SIZE
    }
  };

  if (config != NULL) {
    memcpy (config, &default_config, sizeof (default_config));
  }
}

int tftp_open(
    const char               *hostname,
    const char               *path,
    bool                      is_for_reading,
    const tftp_net_config    *config,
    void                    **tftp_handle
)
{
    struct tftpStream    *tp;
    struct in_addr       farAddress;
    int                  err;

    /*
     * Check parameters
     */
    if (tftp_handle == NULL) {
      return EINVAL;
    }
    *tftp_handle = NULL;
    if (hostname == NULL || path == NULL) {
        return EINVAL;
    }
    if (config != NULL && (
        config->options.window_size < TFTP_WINDOW_SIZE_MIN ||
        config->options.block_size  < TFTP_BLOCK_SIZE_MIN  ||
        config->options.block_size  > TFTP_BLOCK_SIZE_MAX  ) ) {
        return EINVAL;
    }

    /*
     * Create tftpStream structure
     */
    if (get_ip_address( hostname, &farAddress ) == NULL) {
        return ENOENT;
    }
    tp = create_stream( config, &farAddress, is_for_reading );
    if (tp == NULL) {
        return ENOMEM;
    }

    /*
     * Send RRQ or WRQ and wait for reply
     */
    tp->prepare_packet_for_sending = prepare_request_packet_for_sending;
    err = communicate_with_server (tp, path);
    if ( err != 0 ) {
        _Tftp_Destroy (tp);
        return err;
    }

    *tftp_handle = reallocate_stream_buffer ( tp );
    if( *tftp_handle == NULL ) {
        return ENOMEM;
    }

    return 0;
}

/*
 * Read from a TFTP stream
 */
ssize_t tftp_read(
    void          *tftp_handle,
    void          *buffer,
    size_t         count
)
{
    char              *bp;
    struct tftpStream *tp = tftp_handle;
    int               nwant;
    int               err;

    if (tp == NULL || !tp->is_for_reading || buffer == NULL)
        return -EIO;

    /*
     * Read till user request is satisfied or EOF is reached
     */
    bp = buffer;
    nwant = count;
    while (nwant) {
        if (tp->nleft) {
            int ncopy;
            if (nwant < tp->nleft)
                ncopy = nwant;
            else
                ncopy = tp->nleft;
            memcpy (bp, &tp->receive_buf->tftpDATA.data[tp->nused], ncopy);
            tp->nused += ncopy;
            tp->nleft -= ncopy;
            bp += ncopy;
            nwant -= ncopy;
            if (nwant == 0)
                break;
        }
        if (tp->at_eof) {
            break;
        }

        /*
         * Wait for the next packet
         */
        tp->retransmission_error_code = -EIO;
        err = communicate_with_server(tp, NULL);
        if (err == tp->retransmission_error_code) {
          return -EIO;
        }
        /*
         * If communicate_with_server() returns an error, either
         *    * an error message from the server was received or
         *    * an error message was already sent to the server
         * Setting tp->at_eof true, prevents all further calls to
         * communicate_with_server() and suppresses the sending of
         * an error message to the server by tftp_close().
         */
        if (err != 0) {
            tp->at_eof = true;
            return -err;
        }
    }
    return count - nwant;
}

/*
 * Flush a write buffer and wait for acknowledgement
 *
 * This function returns only if there is at least one packet buffer free
 * in the tp->send_buf.  This ensures that tftp_write() can store
 * further data for sending in this free packet buffer.
 *
 * When the end of file has been reached (i.e. tftp_close() called this
 * function), this function returns only after all packets
 * in the write buffer have been send and acknowledged (or if an error
 * occurred).
 */
static int rtems_tftp_flush (struct tftpStream *tp)
{
    int err;

    if (tp->at_eof) {
        return 0;
    }

    do {
        err = communicate_with_server(tp, NULL);
        /*
         * If communicate_with_server() returns an error, either
         *    * an error message from the server was received or
         *    * an error message was already sent to the server
         * Setting tp->at_eof true, prevents all further calls to
         * communicate_with_server() and suppresses the sending of
         * an error message to the server by tftp_close().
         */
        if (err != 0) {
            tp->at_eof = true;
            return err;
        }
    } while(
         (int32_t) tp->blocknum_last_filled + 1 >=
         tp->blocknum_of_first_packet_of_window + tp->send_buf_size_in_pkts ||
         /*
          * tp->blocknum_eof_block == tp->blocknum_last_filled
          * holds only true when the user invoked tftp_close().
          */
         (tp->blocknum_eof_block == tp->blocknum_last_filled &&
         tp->blocknum_of_first_packet_of_window <=
         (int32_t) tp->blocknum_eof_block)
    );

    return 0;
}

/*
 * Close a TFTP stream
 */
int tftp_close(
    void *tftp_handle
)
{
    struct tftpStream *tp = tftp_handle;
    int                e = 0;

    if (tp == NULL) {
        return 0;
    }

    if (!tp->is_for_reading) {
        tp->blocknum_last_filled++;
        tp->blocknum_eof_block = tp->blocknum_last_filled;
        e = rtems_tftp_flush (tp);
        tp->at_eof = true;
    }
    if (!tp->at_eof && !tp->firstReply) {
        /*
         * Tell the other end to stop
         */
        rtems_interval ticksPerSecond;
        send_error (
            tp,
            &tp->farAddress,
            TFTP_ERROR_CODE_NO_USER,
            "User (client) stopped reading or "
            "server stopped sending packets (timeout)"
        );
        ticksPerSecond = rtems_clock_get_ticks_per_second();
        rtems_task_wake_after (1 + ticksPerSecond / 10);
    }
    _Tftp_Destroy (tp);
    return e;
}

ssize_t tftp_write(
    void            *tftp_handle,
    const void      *buffer,
    size_t           count
)
{
    const char        *bp;
    struct tftpStream *tp = tftp_handle;
    int               nleft, nfree, ncopy;
    int               err;
    union tftpPacket *send_buf;

    /*
     * Bail out if an error has occurred
     */
    if (tp == NULL || tp->is_for_reading || tp->at_eof || buffer == NULL) {
        return -EIO;
    }

    /*
     * Write till user request is satisfied
     * Notice that the buffer is flushed as soon as it is filled rather
     * than waiting for the next write or a close.  This ensures that
     * the flush in close writes a less than full buffer so the far
     * end can detect the end-of-file condition.
     */
    bp = buffer;
    nleft = count;
    while (nleft) {
        nfree = tp->block_size - tp->nused;
        if (nleft < nfree)
            ncopy = nleft;
        else
            ncopy = nfree;
        send_buf = get_send_buffer_packet (tp, tp->blocknum_last_filled + 1);
        memcpy (&send_buf->tftpDATA.data[tp->nused], bp, ncopy);
        tp->nused += ncopy;
        nleft -= ncopy;
        bp += ncopy;
        if (tp->nused == tp->block_size) {
            tp->blocknum_last_filled++;
            err = rtems_tftp_flush (tp);
            if (err) {
                return -err;
            }
            tp->nused = 0;
        }
    }
    return count;
}

void _Tftp_Destroy(
    void *tftp_handle
)
{
    struct tftpStream *tp = tftp_handle;
    if (tp == NULL) {
        return;
    }

    if (tp->socket >= 0) {
        close (tp->socket);
    }

    if (tp->receive_buf == tp->send_buf) {
        tp->send_buf = NULL;
    }
    free (tp->receive_buf);
    free (tp->send_buf);
    free (tp);
}