/***************************************************************************//**
  @addtogroup lip_tftp
  @{
  @file lip_tftp.c
  @brief Файл содержит реализацию модуля @ref lip_tftp

  @author Borisov Alexey <borisov@lcard.ru>
 ******************************************************************************/

#include "lip_config.h"


#if defined LIP_USE_TFTP_SERVER || defined LIP_USE_TFTP_CLIENT

#include "lip/misc/lip_rand_cfg_defs.h"

#ifndef LIP_USE_UDP
    #error "tftp requires udp: define LIP_USE_UPD in lip_conf.h"
#endif

#if !LIP_RAND_ENABLE
    #error "tftp requires pseudo-random generator: define  LIP_CFG_RAND_ENABLE in lip_config.h"
#endif

#if !defined LIP_TFTP_SUPPORT_RECV && !defined LIP_TFTP_SUPPORT_SEND
    #error "you must sepcify supported transfer directions for tftp: define LIP_TFTP_SUPPORT_RECV or LIP_TFTP_SUPPORT_SEND in lip_conf.h"
#endif

#include "lip/app/tftp/lip_tftp.h"
#include "lip/transport/udp/lip_udp_private.h"
#include "lip/misc/lip_rand.h"
#include "lip/net/ipv4/lip_ipv4.h"
#include "ltimer.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef LIP_TFTP_INITIAL_DELAY
    #define LIP_TFTP_INITIAL_DELAY  50
#endif

#ifndef LIP_TFPT_SELECT_TID_ATTEMPT_CNT
    #define LIP_TFPT_SELECT_TID_ATTEMPT_CNT  8
#endif

#ifndef LIP_TFTP_REQUEST_CNT
    #define LIP_TFTP_REQUEST_CNT  1
#endif

#ifndef LIP_TFTP_BLOCK_SIZE_MAX
    #define LIP_TFTP_BLOCK_SIZE_MAX 1428
#endif


/* опкоды пакетов TFTP */
#define LIP_TFTP_OPCODE_RRQ   1
#define LIP_TFTP_OPCODE_WRQ   2
#define LIP_TFTP_OPCODE_DATA  3
#define LIP_TFTP_OPCODE_ACK   4
#define LIP_TFTP_OPCODE_ERR   5
#define LIP_TFTP_OPCODE_OACK  6

#define LIP_TFTP_DEFAULT_BLOCK_SIZE  512
#define LIP_TFTP_BLOCK_SIZE_MIN 8


#define _TFTP_FLAGS_SIZE_OP_PRESENT  0x01
#define _TFTP_FLAGS_SEND_LAST        0x02



typedef struct {
    t_lip_udp_info tx_info; //информация UDP для передачи
    t_lip_tftp_req_direction dir; //направление передачи
    uint8_t rem_addr[LIP_IPV4_ADDR_SIZE]; //адрес сервера
    const char *filename; //имя файла
    enum {
        e_TFTP_STATE_NOOP, //нет передачи
        e_TFTP_STATE_RRQ_WAIT, //начальная задержка
        e_TFTP_STATE_RRQ_SEND, //послали RRQ, ждем подтверждения
        e_TFTP_STATE_OACK_SEND, //передан OACK - ждем подтверждения
        e_TFTP_STATE_ACK_DATA, //послали подтверждение, ждем следующий блок
#ifdef LIP_TFTP_CPL_TIMEOUT
        e_TFTP_STATE_CPL_WAIT //приняли файл, ждем таймаут, чтобы можно было
                        // повторно послать потерянное подтверждение
#endif
    } state;
    t_lclock_ticks last_time; //время последнего приема-передачи пакета
    t_lclock_ticks tout;     //текущая величина таймаута
#ifdef LIP_TFTP_SUPPORT_RECV
    t_lip_tftp_data_rcv_cb rd_cb; //callback на прием блока данных
#endif
#ifdef LIP_TFTP_SUPPORT_SEND
    t_lip_tftp_data_snd_cb snd_cb; //callback на передачу данных
#endif
    t_lip_tftp_err_cb err_cb; //callback на ошибочную ситуацию
    t_lip_tftp_cpl_cb cpl_cb; //callback на завершения передачи
    uint16_t block; //номер блока
    uint16_t retrans; //количество повторов при передачи блока
    uint16_t block_size; //используемый размер блока
#ifdef LIP_TFTP_SUPPORT_SEND
    int snd_size;
    const uint8_t *snd_data;
#endif
    uint32_t flags;
    uint32_t fsize;
    t_ltimer cpl_timer; /* таймер на ожидание после заверщения приема
                        * отдельно, т.к. таймаут повторных пердач может
                        * работать одновременно */
} t_lip_tftp_file_state;

static t_lip_tftp_file_state f_tftp_file_state[LIP_TFTP_REQUEST_CNT];

#ifdef LIP_USE_TFTP_SERVER
    static uint8_t f_port = 0; /* номер прослушиваемого порта. Также является
                                  признаком, что что-то прослушивается */
    static t_lip_tftp_req_cb f_req_cb = NULL; /* callback на прием запроса от клиента */
#endif

#define LIP_TFTP_OPTS_BLKSIZE     "blksize"
#define LIP_TFTP_OPTS_FILE_SIZE   "tsize"

static int f_atoi(const char *str, const char *strend) {
    int t_val = 0;
    for (; (*str >= '0') && (*str <= '9') && (str != strend); ++str) {
        t_val *= 10;
        t_val += *str - '0';
    }

    return t_val;
}

/* получаем первую не используемую структуру описания передачи файла */
static t_lip_tftp_file_state *f_get_free_file_state(void) {
    unsigned i;
    t_lip_tftp_file_state* ret = NULL;
    for (i = 0; (ret == NULL) && (i < LIP_TFTP_REQUEST_CNT); i++) {
        if (f_tftp_file_state[i].state==e_TFTP_STATE_NOOP)
            ret = &f_tftp_file_state[i];
    }

    /* если нет свободного состояния, то пробуем использовать то, которое
     * сохранено лишь для подтверждения повторных передач */
#ifdef LIP_TFTP_CPL_TIMEOUT
    for (i = 0; (ret == NULL) && (i < LIP_TFTP_REQUEST_CNT); i++) {
        if (f_tftp_file_state[i].state==e_TFTP_STATE_CPL_WAIT) {
            lip_udp_port_unlisten(f_tftp_file_state[i].tx_info.src_port);
            f_tftp_file_state[i].state=e_TFTP_STATE_NOOP;
            ret = &f_tftp_file_state[i];
        }
    }
#endif
    return ret;
}

#ifdef LIP_USE_TFTP_SERVER
/* получаем состояние по удаленному порту */
static t_lip_tftp_file_state *f_get_file_state_by_rem_port(uint16_t port,
                                                           const uint8_t *addr) {
    unsigned i;
    t_lip_tftp_file_state *ret = NULL;
    for (i = 0; (ret == NULL) && (i < LIP_TFTP_REQUEST_CNT); i++) {
        if ((f_tftp_file_state[i].state != e_TFTP_STATE_NOOP) &&
#ifdef LIP_TFTP_CPL_TIMEOUT
                (f_tftp_file_state[i].state != e_TFTP_STATE_CPL_WAIT) &&
#endif
                (f_tftp_file_state[i].tx_info.dst_port == port) &&
                lip_ipv4_addr_equ(addr, f_tftp_file_state[i].tx_info.dst_addr)) {
            ret = &f_tftp_file_state[i];
        }
    }
    return ret;
}
#endif

/* получаем структуру описания файла соответствующую используемому на приемной
 * стороне порту UDP (TID) */
static t_lip_tftp_file_state *f_get_pack_file_state(const t_lip_udp_info *info) {
    unsigned i;
    t_lip_tftp_file_state *ret = NULL;
    for (i = 0; (ret == NULL) && (i < LIP_TFTP_REQUEST_CNT); i++) {
        if ((f_tftp_file_state[i].tx_info.src_port == info->dst_port)
                && (f_tftp_file_state[i].state != e_TFTP_STATE_NOOP) &&
                lip_ipv4_addr_equ(info->src_addr, f_tftp_file_state[i].tx_info.dst_addr)) {
            ret = &f_tftp_file_state[i];
        }
    }
    return ret;
}



/*************************************************************************
 * Выбор id для передачи - выбирается из динамически назначаемых портов
 *************************************************************************/
static uint16_t f_tftp_gen_tid(void) {
    return lip_rand_range(LIP_DYNAMIC_PORT_LAST - LIP_DYNAMIC_PORT_FIRST)
            + LIP_DYNAMIC_PORT_FIRST;
}


/*************************************************************************
 * посылка пакета об ошибке
 *************************************************************************/
static void f_tftp_send_err(t_lip_udp_info *tx_info, uint16_t err_code,
                            const char *err_str) {
    uint8_t *tx_buf;
    int max_size;
    err_code--;

    tx_info->data_size = 5 + (err_str ? strlen(err_str) : 0);
    tx_buf = lip_udp_prepare(tx_info, &max_size);
    if ((tx_buf != NULL) && (max_size >=tx_info->data_size)) {
        *tx_buf++ = (LIP_TFTP_OPCODE_ERR >> 8)&0xFF;
        *tx_buf++ = LIP_TFTP_OPCODE_ERR&0xFF;
        *tx_buf++ = (err_code >> 8)&0xFF;
        *tx_buf++ = err_code&0xFF;
        if (err_str != NULL) {
            strcpy((char*)tx_buf, err_str);
        } else {
            *tx_buf++ = 0;
        }
        lip_udp_flush(tx_info->data_size);
    }
}

#ifdef LIP_TFTP_SUPPORT_RECV
/**************************************************
 * посылаем подтверждение на текущий блок
 **************************************************/
static void f_tftp_send_ack(t_lip_tftp_file_state *state) {
    uint8_t *tx_buf;
    int max_size;

    state->tx_info.data_size = 4;
    tx_buf = lip_udp_prepare(&state->tx_info, &max_size);
    if ((tx_buf != NULL) && (max_size >=state->tx_info.data_size)) {
        *tx_buf++ = (LIP_TFTP_OPCODE_ACK >> 8)&0xFF;
        *tx_buf++ = LIP_TFTP_OPCODE_ACK&0xFF;
        *tx_buf++ = (state->block >> 8)&0xFF;
        *tx_buf++ = state->block&0xFF;

        state->last_time = lclock_get_ticks();
        lip_udp_flush(state->tx_info.data_size);
    }
}
#endif


#ifdef LIP_TFTP_SUPPORT_OPTS

static void f_put_int(uint8_t **pkt, uint32_t val) {
    uint8_t *put_pos = *pkt;
    if (val == 0) {
        *put_pos = '0';
    } else {
        uint32_t mul;
        for (mul = 1; mul < val; mul *= 10) {}

        if (mul != val)
            mul/=10;

        for (; mul; mul /= 10) {
            uint32_t dig = val/mul;
            val = val % mul;
            dig += '0';
            *put_pos++ = dig;
        }
    }
    *put_pos++ = 0;
    *pkt = put_pos;
}

static void f_tftp_send_oack(t_lip_tftp_file_state *state) {
    uint8_t *tx_buf, *start_buf;
    int max_size;

    start_buf = tx_buf = lip_udp_prepare(&state->tx_info, &max_size);
    if ((tx_buf != NULL) && (max_size >= state->tx_info.data_size)) {
        *tx_buf++ = (LIP_TFTP_OPCODE_OACK >> 8)&0xFF;
        *tx_buf++ = LIP_TFTP_OPCODE_OACK&0xFF;
        if (state->block_size != LIP_TFTP_DEFAULT_BLOCK_SIZE) {
            strcpy((char*)tx_buf, LIP_TFTP_OPTS_BLKSIZE);
            tx_buf+=strlen(LIP_TFTP_OPTS_BLKSIZE)+1;

            f_put_int(&tx_buf, state->block_size);
        }
        if ((state->flags & _TFTP_FLAGS_SIZE_OP_PRESENT) && (state->fsize != 0)) {
            strcpy((char*)tx_buf, LIP_TFTP_OPTS_FILE_SIZE);
            tx_buf+=strlen(LIP_TFTP_OPTS_FILE_SIZE)+1;
            f_put_int(&tx_buf, state->fsize);
        }

        state->last_time = lclock_get_ticks();
        lip_udp_flush(tx_buf-start_buf);
    }
}

#endif

/*****************************************************************
 *  передача запроса на чтение или запись файла
 *****************************************************************/
static int f_tftp_send_rq(t_lip_tftp_file_state *state) {
    int max_size = 0;
    int name_len = strlen(state->filename);
    int err = 0;
    state->tx_info.data_size = name_len + 3 + sizeof(LIP_TFTP_MODE_OCTET);
    uint8_t *tx_buf = lip_udp_prepare(&state->tx_info, &max_size);

    if (tx_buf == NULL) {
        err = max_size;
    } else if (max_size < state->tx_info.data_size) {
        err = LIP_ERR_TXBUF_TOO_SMALL;
    } else {
        uint16_t op = state->dir == LIP_TFTP_DIR_RECV ?
                    LIP_TFTP_OPCODE_RRQ : LIP_TFTP_OPCODE_WRQ;

        *tx_buf++ = (op >> 8)&0xFF;
        *tx_buf++ = op&0xFF;
        memcpy(tx_buf, state->filename, name_len+1);
        tx_buf+=(name_len+1);
        memcpy(tx_buf, LIP_TFTP_MODE_OCTET, sizeof(LIP_TFTP_MODE_OCTET));

        state->last_time = lclock_get_ticks();
        err = lip_udp_flush(state->tx_info.data_size);
    }

    return err;
}

#ifdef LIP_TFTP_SUPPORT_SEND
    static int f_process_send_block(t_lip_tftp_file_state *state, int flags) {
        int err = 0;
        const char *err_str = NULL;
        if (state->snd_cb != NULL) {
            uint8_t *tx_buf;
            int max_size;

            tx_buf = lip_udp_prepare(&state->tx_info, &max_size);
            /* если нет свободного буфера, то считаем, что это не критическая
               ошибка и мы можем потом заново повторить попытку отправки */
            if ((tx_buf != NULL) && (max_size >= (state->block_size+4))) {
                uint32_t offs = state->block*state->block_size;
                uint16_t block = flags & LIP_TFTP_SND_FLAG_RETRANS ? state->block : state->block+1;
                if (flags & LIP_TFTP_SND_FLAG_RETRANS)
                    offs -= state->block;

                *tx_buf++ = (LIP_TFTP_OPCODE_DATA >> 8)&0xFF;
                *tx_buf++ = LIP_TFTP_OPCODE_DATA&0xFF;
                *tx_buf++ = (block >> 8)&0xFF;
                *tx_buf++ = block&0xFF;

                if (!(flags & LIP_TFTP_SND_FLAG_RETRANS))
                    state->snd_size = state->block_size;

                err = state->snd_cb(state, tx_buf, &state->snd_size,
                                    offs, flags, &err_str);
                if (!err && !(flags & LIP_TFTP_SND_FLAG_RETRANS)) {
                    if (state->snd_size!=state->block_size)
                        state->flags |= _TFTP_FLAGS_SEND_LAST;
                    state->block++;
                }

                if (!err) {
                    state->last_time = lclock_get_ticks();
                    lip_udp_flush(state->snd_size+4);
                }
            }
        } else {
            err = LIP_TFTP_ERRCODE_ACCESS_VIOLATION;
        }

        if (err) {
            f_tftp_send_err(&state->tx_info, err, err_str);
            lip_udp_port_unlisten(state->tx_info.src_port);
            state->state = e_TFTP_STATE_NOOP;
        }
        return err;
    }

#endif

static void f_retransmit(t_lip_tftp_file_state* state) {
    /* выполняем повторную передачу */
    if (state->state==e_TFTP_STATE_RRQ_SEND) {
        f_tftp_send_rq(state);
#ifdef LIP_TFTP_SUPPORT_OPTS
    } else if (state->state==e_TFTP_STATE_OACK_SEND) {
        f_tftp_send_oack(state);
#endif
    } else if (state->state==e_TFTP_STATE_ACK_DATA) {
#ifdef LIP_TFTP_SUPPORT_RECV
        if (state->dir==LIP_TFTP_DIR_RECV)
            f_tftp_send_ack(state);
#endif
#ifdef LIP_TFTP_SUPPORT_SEND
        if (state->dir==LIP_TFTP_DIR_SEND)
            f_process_send_block(state, LIP_TFTP_SND_FLAG_RETRANS);
#endif
    }
}

/*********************************************************************************************
 *   Обработка входных пакетов - в данном случае это данные и ошибки
 **********************************************************************************************/
static t_lip_errs f_lip_tftp_process_packet(uint8_t* pkt, size_t size,
                                            const t_lip_udp_info *info) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_tftp_file_state* state = f_get_pack_file_state(info);
    if (state == NULL) {
        err = LIP_ERR_UDP_UNREACHABLE_PORT;
    } else {
        uint16_t opt;

        //получаем код операции
        opt = ((uint16_t)pkt[0]<<8) + pkt[1];
        pkt+=2;

        /* если находимся в состоянии приема файла */
        if ((state->state == e_TFTP_STATE_RRQ_SEND)
             || (state->state == e_TFTP_STATE_OACK_SEND)
                || (state->state == e_TFTP_STATE_ACK_DATA)
#ifdef LIP_TFTP_CPL_TIMEOUT
                || (state->state == e_TFTP_STATE_CPL_WAIT)
#endif
                ) {
            /* пришли новые данные */
            if (((state->dir == LIP_TFTP_DIR_RECV) && (opt == LIP_TFTP_OPCODE_DATA))
                    || ((state->dir==LIP_TFTP_DIR_SEND) && (opt==LIP_TFTP_OPCODE_ACK))) {
                const char* err_str = 0;
                t_lip_tftp_err_code tftp_err = LIP_TFTP_ERRCODE_OK;
                //получаем номер блока
                uint16_t block = ((uint16_t)pkt[0]<<8) + pkt[1];
                pkt+=2;

                if ((state->state != e_TFTP_STATE_RRQ_SEND) &&
                        (state->tx_info.dst_port != info->src_port)) {
                    /* пришел пакет с TID, которого мы не ждали
                     * нужно просто послать ошибку и продолжить принимать */
                    err = LIP_ERR_TFTP_SRC_TID;
                    tftp_err = LIP_TFTP_ERRCODE_UNKNOW_TID;
#ifdef LIP_TFTP_SUPPORT_OPTS
                } else if (state->state == e_TFTP_STATE_OACK_SEND) {
                    if (block==((state->dir==LIP_TFTP_DIR_RECV) ? 1 : 0)) {
                        state->state = e_TFTP_STATE_ACK_DATA;
                    } else {
                        err = LIP_ERR_TFTP_BLOCK_NUM;
                    }
#endif
#ifdef LIP_TFTP_CPL_TIMEOUT
                } else if (state->state == e_TFTP_STATE_CPL_WAIT) {
                    err = LIP_ERR_TFTP_BLOCK_NUM;
#endif
                } else if (block != (state->block + ((state->dir==LIP_TFTP_DIR_RECV) ? 1 : 0))) {
                    /* проверяем номер блока. при приеме - это должны быть следующие
                     * данные, при передаче - подтверждение на переданные данные (с тем же номером) */
                    err = LIP_ERR_TFTP_BLOCK_NUM;
                } else if (state->state == e_TFTP_STATE_RRQ_SEND) {
                    /* если это первый блок - сохраняем порт сервера
                     * (его случайный TID) */
                    state->tx_info.dst_port = info->src_port;
                    state->state = e_TFTP_STATE_ACK_DATA;
                }

#ifdef LIP_TFTP_SUPPORT_RECV
                /* если данные правильные - вызываем callback на принятые данные */
                if (!err && (state->dir == LIP_TFTP_DIR_RECV)) {
                    /* получаем размер данных - вычитаем размер опкода и номера блока */
                    size-=4;
                    /* обрабатываем принятые данные */
                    if (state->rd_cb != NULL) {
                        tftp_err = state->rd_cb(state, pkt, size, state->block*state->block_size,
                                           &err_str);
                    }

                    /* если принято меньше размера блока => последний блок =>
                     * конец отправки */
                    if ((tftp_err == LIP_TFTP_ERRCODE_OK) && (size<state->block_size)) {
                        if (state->cpl_cb != NULL)
                            tftp_err = state->cpl_cb(state, &err_str);

                        if (tftp_err == LIP_TFTP_ERRCODE_OK) {
#ifdef LIP_TFTP_CPL_TIMEOUT
                            /* если задано время, то после приема еще немного ждем
                             * чтобы можно было в случае, если хост не принял ack
                             * послать еще один                  */
                            ltimer_set(&state->cpl_timer, LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_CPL_TIMEOUT));
                            state->state = e_TFTP_STATE_CPL_WAIT;
#else
                            lip_udp_port_unlisten(state->tx_info.src_prot);
                            state->state = e_TFTP_STATE_NOOP;

                            state = NULL;
#endif
                        }
                    }

                    if ((tftp_err == LIP_TFTP_ERRCODE_OK) && (state != NULL)) {
                        /* переходим к след. блоку */
                        state->block++;
                    }
                }
#endif
#ifdef LIP_TFTP_SUPPORT_SEND
                /* если данные правильные - вызываем callback на принятые данные */
                if (!err && (state->dir == LIP_TFTP_DIR_SEND)) {
                    if (state->flags&_TFTP_FLAGS_SEND_LAST) {
                        lip_udp_port_unlisten(state->tx_info.src_port);
                        state->state = e_TFTP_STATE_NOOP;
                        if (state->cpl_cb != NULL)
                            tftp_err = state->cpl_cb(state, &err_str);
                        if (tftp_err == LIP_TFTP_ERRCODE_OK)
                            state = NULL;
                    } else {
                        err = f_process_send_block(state, 0);
                        if (err != LIP_ERR_SUCCESS)
                            tftp_err = LIP_TFTP_ERRCODE_NOTDEF;
                    }
                }
#endif
                /* успешно приняли все данные */
                if (!err && (tftp_err == LIP_TFTP_ERRCODE_OK) && (state != NULL)) {
                    /*************************************************************
                     * при отправке без повторных передач можем скорректировать
                     * таймаут - берем в 2 раза больше времени rtt, но не меньше
                     * изначального таймаута
                     *************************************************************/
                    if ((state->retrans == 0) && (state->tout >
                                 LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_INIT))) {
                        state->tout = (lclock_get_ticks() - state->last_time) << 1;
                        if (state->tout < LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_INIT)) {
                            state->tout = LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_INIT);
                        }
                    }
                    //сбрасываем кол-во повторных передач
                    state->retrans = 0;
                }

                if (state != NULL) {
                    if (tftp_err == LIP_TFTP_ERRCODE_OK) {
                        /* посылаем подтверждение
                         *(если неверный блок - то все равно подтверждение, т.к.
                         *   возможно другая сторона потеряла наш ACK и мы его повторяем)
                         * в соответствии с rfc1123 4.2.3.1 принимающая может повтрять
                         * ACK по out-of-order пакету, в отличии от передающей,
                         * которая не должна отвечать на неверный ACK, для избежания
                         * "Sourcerer's Apprentice Syndrome" */
                        if ((state->dir==LIP_TFTP_DIR_RECV) &&
                                ((state->state == e_TFTP_STATE_ACK_DATA)
#ifdef LIP_TFTP_CPL_TIMEOUT
                                 || (state->state == e_TFTP_STATE_CPL_WAIT)
#endif
                                 )) {
                            f_tftp_send_ack(state);
                        }
                    } else if (tftp_err == LIP_TFTP_ERRCODE_UNKNOW_TID) {
                        /* на неверный TID шлем сообщение не разрывая передачу.
                         * временно изменяем порт назначения, чтобы послать именно
                         * на тот порт, откуда пришло сообщение.
                         * Больше никаких действий - это не критическая ошибка */
                        uint16_t dst_port = state->tx_info.dst_port;
                        state->tx_info.dst_port = info->src_port;
                        f_tftp_send_err(&state->tx_info, LIP_TFTP_ERRCODE_UNKNOW_TID, "unknown transfer ID");
                        state->tx_info.dst_port = dst_port;
                    } else {
                        /* пользовтальская ошибка - шлем пользовательское сообщение
                         * и убиваем соединение */
                        f_tftp_send_err(&state->tx_info, err, err_str);
                        lip_udp_port_unlisten(state->tx_info.src_port);
                        state->state = e_TFTP_STATE_NOOP;
                        state = NULL;
                        if (err == LIP_ERR_SUCCESS) {
                            err = LIP_ERR_TFTP_PROTOCOL_ERR;
                        }
                    }
                }
            }
        }


        if (opt == LIP_TFTP_OPCODE_ERR) {
            /* пришло сообщение об ошибке=>вызываем пользовательски callback
             * и убиваем соединение */
            lip_udp_port_unlisten(state->tx_info.src_port);
            state->state = e_TFTP_STATE_NOOP;

            if (state->err_cb != NULL) {
                uint16_t err_code = ((uint16_t)pkt[0]<<8) + pkt[1];
                state->err_cb(state, err_code+1);
            }
        }
    }
    return err;
}


static void* f_start_req(t_lip_tftp_req_direction dir,
                               const char* filename, uint16_t udp_port,
                               const uint8_t* host_addr,
                               t_lip_tftp_data_rcv_cb rcv_cb, t_lip_tftp_data_snd_cb snd_cb,
                               t_lip_tftp_err_cb err_cb, t_lip_tftp_cpl_cb cpl_cb)
{
    unsigned i;
    int err;
    t_lip_tftp_file_state* state = f_get_free_file_state();
    if (!state)
        return 0;

    /************** заполняем инфу для передачи UDP  *****************/
    //обнуляем UDP-информацию (на случай появления расширяющих полей в дальнейшем)
    memset(&state->tx_info, 0, sizeof(state->tx_info));

    /* выбираем TID - он же порт источника - выбираем случайно, но нужно
     * выбрать еще не прослушиваемый - на всякий случай делаем
     * LIP_TFPT_SELECT_TID_ATTEMPT_CNT попыток попасть на непросл. порт
     */
    for (err = LIP_ERR_UDP_PORT_ALREADY_LISTENED, i = 0;
            (err == LIP_ERR_UDP_PORT_ALREADY_LISTENED)
                    && (i < LIP_TFPT_SELECT_TID_ATTEMPT_CNT); i++)
    {
        //в качесве порта источника выбирается случайный udp-порт из динамических
        state->tx_info.src_port = f_tftp_gen_tid();
        //начинаем прослушивать выбранный порт
        err = lip_udp_port_listen(state->tx_info.src_port, f_lip_tftp_process_packet);
    }

    if (!err)
    {
        state->tx_info.dst_port = udp_port;
        state->tx_info.data_size = 0;
        state->tx_info.ip_type = LIP_IPTYPE_IPv4;
        state->flags = 0;
        /* адрес удаленной стороны указывает на поле внутри структуры состояния */
        state->tx_info.dst_addr = state->rem_addr;
        /* копируем сам адрес из параметра функции в поле в состоянии функции */
        memcpy(state->rem_addr, host_addr, LIP_IPV4_ADDR_SIZE);
        //инициализируем параметры TFTP передачи
        state->filename = filename;
#ifdef LIP_TFTP_SUPPORT_RECV
        state->rd_cb = rcv_cb;
#endif
#ifdef LIP_TFTP_SUPPORT_SEND
        state->snd_cb = snd_cb;
#endif
        state->err_cb = err_cb;
        state->cpl_cb = cpl_cb;
        state->block = 0;
        state->retrans = 0;
        state->last_time = lclock_get_ticks();
        state->dir = dir;
        state->block_size = LIP_TFTP_DEFAULT_BLOCK_SIZE;
        /*добавляем случайную задержку, чтобы снизить вероятность
         * одновременной посылки */
        state->tout = lip_rand_range(LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_INITIAL_DELAY));

        state->state = e_TFTP_STATE_RRQ_WAIT;

        return state;
    }
    return 0;
}



void lip_tftp_init(void)
{
    unsigned i;
    for (i=0; i < LIP_TFTP_REQUEST_CNT; i++)
        f_tftp_file_state[i].state=e_TFTP_STATE_NOOP;
#ifdef LIP_USE_TFTP_SERVER
    f_port = 0;
    f_req_cb = 0;
#endif
}

void lip_tftp_close(void)
{
#ifdef LIP_USE_TFTP_SERVER
    lip_tftp_listen_stop();
#endif
    lip_tftp_abort_all(LIP_TFTP_ERRCODE_NOTDEF, "host shutdown");
}


/***************************************************************************//**
 * Функция завершает передачу для указанного состояния передачи.
 * Удаленной стороне пересылается пакет ERR с указанной ошибкой и текстом,
 * после чего состояние удаляется и вызывается callback об ошибке.
 *
 * @param[in] pstate      Указатель на состояние передачи
 * @param[in] err         Код ошибки TFTP
 * @param[in] err_str     Строка с описанием ошибки (или NULL)
 ******************************************************************************/
void lip_tftp_abort(void* pstate, t_lip_tftp_err_code err, const char* err_str) {
    t_lip_tftp_file_state* state = (t_lip_tftp_file_state*)pstate;
    if ((state->state == e_TFTP_STATE_RRQ_SEND)
         || (state->state == e_TFTP_STATE_OACK_SEND)
            || (state->state == e_TFTP_STATE_ACK_DATA)) {
        f_tftp_send_err(&state->tx_info, err, err_str);
        if (state->err_cb!=NULL)
            state->err_cb(state, err);
    }
    lip_udp_port_unlisten(state->tx_info.src_port);
    state->state = e_TFTP_STATE_NOOP;
}


/***************************************************************************//**
 * Завершение всех активных передач для TFTP-модуля с указанным кодом ошибки.
 * Для всех передач просто вызывается lip_tftp_abort().
 * @param[in] err         Код ошибки TFTP
 * @param[in] err_str     Строка с описанием ошибки (или NULL)
 ******************************************************************************/
void lip_tftp_abort_all(t_lip_tftp_err_code err, const char* err_str) {
    unsigned i;
    for (i=0; i < LIP_TFTP_REQUEST_CNT; i++) {
        if (f_tftp_file_state[i].state!=e_TFTP_STATE_NOOP)
            lip_tftp_abort(&f_tftp_file_state[i], err, err_str);
    }
}




#ifdef LIP_TFTP_SUPPORT_RECV
/***************************************************************************//**
   Запуск запроса чтения файла из удаленного TFTP-сервера
   @param[in] filename     Имя запрашиваемого файла. Должно оставаться без
                           изменения (так как не копируется) хотя бы до
                           приема первого пакета данных.
   @param[in] udp_port     Прослушиваемый удаленным сервером UDP-порт
                           (обычно - #LIP_UDP_PORT_TFTP)
   @param[in] host_addr    IPv4-адресс удаленного сервера
   @param[in] rcv_cb     Функция, вызываемая при приема пакета данных
   @param[in] err_cb     Функция, вызываемая при ошибке передачи или NULL
   @param[in] cpl_cb     Функция, вызываемая при завершении передачи или NULL
   @return Указтель на состояние передачи или NULL, если нет свободного состояния
********************************************************************************/
void* lip_tftp_req_read(const char *filename, uint16_t udp_port,
                        const uint8_t *host_addr,  t_lip_tftp_data_rcv_cb rcv_cb,
                        t_lip_tftp_err_cb err_cb, t_lip_tftp_cpl_cb cpl_cb) {
    return f_start_req(LIP_TFTP_DIR_RECV, filename, udp_port, host_addr,
                       rcv_cb, NULL, err_cb, cpl_cb);
}
#endif


#ifdef LIP_TFTP_SUPPORT_SEND
/***************************************************************************//**
   Запуск запроса на запись файла удаленному TFTP-серверу
   @param[in] filename     Имя записываемого файла. Должно оставаться без
                           изменения (так как не копируется) хотя бы до
                           передачи первого пакета данных.
   @param[in] udp_port     Прослушиваемый удаленным сервером UDP-порт
                           (обычно - #LIP_UDP_PORT_TFTP)
   @param[in] host_addr    IPv4-адресс удаленного сервера
   @param[in] snd_cb     Функция, вызываемая для подготовки данных на передачу
   @param[in] err_cb     Функция, вызываемая при ошибке передачи или NULL
   @param[in] cpl_cb     Функция, вызываемая при завершении передачи или NULL
   @return Указтель на состояние передачи или NULL, если нет свободного состояния
********************************************************************************/
void *lip_tftp_req_write(const char *filename, uint16_t udp_port, const uint8_t *host_addr,
                         t_lip_tftp_data_snd_cb snd_cb, t_lip_tftp_err_cb err_cb,
                         t_lip_tftp_cpl_cb cpl_cb) {
    return f_start_req(LIP_TFTP_DIR_SEND, filename, udp_port, host_addr,
                       NULL, snd_cb, err_cb, cpl_cb);
}
#endif


/************************************************************************
 *  Продвижение TFTP - проверка таймаута и повторные передачи
 * **********************************************************************/
void lip_tftp_pull(void) {
    unsigned i;
    for (i=0; i < LIP_TFTP_REQUEST_CNT; i++) {
        t_lip_tftp_file_state *state=  &f_tftp_file_state[i];
        if (state->state != e_TFTP_STATE_NOOP) {
            //повторная передача пакетов
            if (((lclock_get_ticks() - state->last_time) > state->tout)
#ifdef LIP_TFTP_CPL_TIMEOUT
                    && (state->state != e_TFTP_STATE_CPL_WAIT)
#endif
                    ) {

                /* если мы просто выжидаем таймаут перед посылкой первого запроса,
                 * то шлем его по таймауту и инициализируем таймаут повторной
                 * передачи на дефолтное значение */
                if (state->state == e_TFTP_STATE_RRQ_WAIT) {
                    state->tout = LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_INIT);
                    state->state = e_TFTP_STATE_RRQ_SEND;
                    f_tftp_send_rq(state);
                } else if (state->retrans++>=LIP_TFTP_RETRANS_CNT) {
                    /* проверяем, исчерпано ли макс. кол-во повторных передач,
                       если да, то соединение можно убивать */
                    state->state = e_TFTP_STATE_NOOP;
                    lip_udp_port_unlisten(state->tx_info.src_port);
                    /* шлем дополнительно ошибку, что разорвали соединение, чтобы если другая
                     * сторона тоже разоравала соединение. хотя это и не требуется, но
                     * может быть полезно. Так как такого когда ошибки нет, посылаем 0-ой код
                     */
                    f_tftp_send_err(&state->tx_info, LIP_TFTP_ERRCODE_NOTDEF,
                                    "retransmit limit exceeded. transfer aborted");
                    if (state->err_cb != NULL) {
                        state->err_cb(state, LIP_ERR_TFTP_TIMEOUT);
                    }
                } else {
                    f_retransmit(state);
                    /* увеличиваем таймаут в два раза (но не больше заданного) */
                    state->tout <<=1;
#ifdef LIP_TFTP_RETRANS_TIMEOUT_MAX
                    if (state->tout > LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_MAX)) {
                        state->tout = LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_MAX);
                    }
#endif
                }
            }

#ifdef LIP_TFTP_CPL_TIMEOUT
            //проверяем время завершения последнего таймаута
            if ((state->state == e_TFTP_STATE_CPL_WAIT) && ltimer_expired(&state->cpl_timer)) {
                lip_udp_port_unlisten(state->tx_info.src_port);
                state->state = e_TFTP_STATE_NOOP;
            }
#endif
        }
    }
}



#ifdef LIP_USE_TFTP_SERVER

static t_lip_errs f_srv_process_req(uint8_t *pkt, size_t size, const t_lip_udp_info *info) {
    uint16_t opt;
    static t_lip_udp_info tx_info;
    if (size < 4)
        return LIP_ERR_RXBUF_TOO_SMALL;

    /* проверяем, не повтор ли это предыдущего запроса */
    t_lip_tftp_file_state *state = f_get_file_state_by_rem_port(info->src_port, info->src_addr);
    if (state != NULL) {
        /* если уже идет передача для этого порта, то вероятнее всего это
         * просто повторный запрос - ретрансмитим последний пакет, но таймаут
         * на всякий случай не увеличиваем */
        f_retransmit(state);
        return LIP_ERR_SUCCESS;
    }

    /* получаем код операции */
    opt = ((uint16_t)pkt[0]<<8) + pkt[1];
    pkt+=2;
    size-=2;
    /* по стандартному порту принимаем только запросы на чтение или запись */
    if ((opt==LIP_TFTP_OPCODE_RRQ) || (opt==LIP_TFTP_OPCODE_WRQ)) {
        static t_lip_tftp_recv_req_params params;
        size_t len = 0;
        const char *err_str=0;
        t_lip_tftp_err_code err = LIP_TFTP_ERRCODE_OK;

        tx_info = *info;

        memset(&params,0, sizeof(params));        

        params.dir = opt==LIP_TFTP_OPCODE_WRQ ? LIP_TFTP_DIR_RECV :
                                                LIP_TFTP_DIR_SEND;
        params.udp_info = &tx_info;
        params.filename = (char*)pkt;
        len = strnlen((char*)pkt, size);
        /* в пакете должено быть как имя файла, так и режим */
        if (size == len) {
            err = LIP_TFTP_ERRCODE_NOTDEF;
            err_str = "invalid packet";
        } else {
            size -= (len+1);
            pkt += (len+1);

            params.mode = (char*)pkt;
            len = strnlen(params.mode, size);
            if(size == len) {
                err = LIP_TFTP_ERRCODE_NOTDEF;
                err_str = "invalid packet";
            }
        }

        if (!err) {
            unsigned i;
            /* так как режим может быть в любом регистре - то переводим
             * все к строчным буквам для сравнения */
            for (i=0; i < len; i++) {
                if ((pkt[i]>=0x41) && (pkt[i] <= 0x5A)) {
                    pkt[i] += 0x20;
                }
            }

            /* поддерживаем "netascii" и "octet". "mail" согласно rfc1123
             * поддерживать не нужно */
            if (strcmp(params.mode, LIP_TFTP_MODE_OCTET) &&
                 strcmp(params.mode, LIP_TFTP_MODE_ASCII)) {
                err =  LIP_TFTP_ERRCODE_ILLEGAL_OP;
                err_str = "invalid mode";
            }

            size -= (len+1);
            pkt += (len+1);
        }

#ifdef LIP_TFTP_SUPPORT_OPTS
        while (size > 0) {
            char *cur_opt;
            char *val;

            cur_opt = (char *)pkt;
            len = strnlen(cur_opt, size);
            size -= (len+1);
            pkt += (len+1);
            if (size > 0) {
                val = (char *)pkt;
                len = strnlen(val, size);
                size-=(len+1);
                pkt+=(len+1);

                if (!strcmp(cur_opt, LIP_TFTP_OPTS_BLKSIZE)) {
                    params.block_size = f_atoi(val, (const char *)pkt);
                    /* сервер вправе выбрать меньшее значение block_size,
                     * однако если blocksize меньше минимально доступного,
                     * то игнорируем опцию */
                    if (params.block_size > LIP_TFTP_BLOCK_SIZE_MAX)
                        params.block_size = LIP_TFTP_BLOCK_SIZE_MAX;
                    if (params.block_size < LIP_TFTP_BLOCK_SIZE_MIN)
                        params.block_size = 0;
                } else if (!strcmp(cur_opt, LIP_TFTP_OPTS_FILE_SIZE)) {
                    params.flags |= _TFTP_FLAGS_SIZE_OP_PRESENT;
                    params.file_size = f_atoi(val, (const char *)pkt);
                }
            }
        }
#endif
        if (!err && (f_req_cb != NULL)) {
            err = f_req_cb(&params, &err_str);
        }

        /* если произошла ошибка - то посылаем сообщение отправителю запроса */
        if (err) {
            tx_info.dst_port = info->src_port;
            tx_info.src_port = info->dst_port;
            tx_info.src_addr = 0;
            tx_info.dst_addr = info->src_addr;
            f_tftp_send_err(&tx_info, err, err_str);
        }
    }
    return LIP_ERR_SUCCESS;
}

/***************************************************************************//**
   По вызову данной функции начинается прослушивание указанного UDP-порта
   на предмет приема TFTP-запросов. При приеме действительного запроса будет
   вызвана указанная callback-функция, которя должна решить принять запрос или
   откланить.

   Модуль может прослушивать только один порт и может быть зарегистрирован только
   один callback. При повторном вызове lip_tftp_listen_start() действия предыдущего
   вызова отменяются.

   @param[in] port   UDP-порт для прослушивания (как правило это #LIP_UDP_PORT_TFTP)
   @param[in] cb     Функция, которая будет вызвана при приеме запроса TFTP
   @return           Код ошибки
 ******************************************************************************/
int lip_tftp_listen_start(uint16_t port, t_lip_tftp_req_cb cb) {
    int err;
    lip_tftp_listen_stop();
    err = lip_udp_port_listen(port, f_srv_process_req);
    if (!err) {
        f_port = port;
        f_req_cb = cb;
    }
    return err;
}

/***************************************************************************//**
 * Вызов этой функции отменяет ранее разрешенное с помощью lip_tftp_listen_start()
 * прослушивание порта на приход TFTP-запросов
 ******************************************************************************/
void lip_tftp_listen_stop(void) {
    if (f_port != 0) {
        lip_udp_port_unlisten(f_port);
        f_port = 0;
        f_req_cb = NULL;
    }
}

/***************************************************************************//**
   Данная функция должна вызываться из зарегистрированной с помощью
   lip_tftp_listen_start() функции разрешения приема принятого запроса.

   В функцию необходимо передать полученные от стека параметры и указатели на
   необходимые callback-функции для выполнения передачи файла.

   @param[in] params     Пераметры подтверждаемого запроса.
   @param[in] rcv_cb     Функция, вызываемая при приема пакета данных (обязательна при
                         направлении передачи #LIP_TFTP_DIR_RECV)
   @param[in] snd_cb     Функция, вызываемая для подготовки данных на передачу (обязательна
                         при направлении передачи #LIP_TFTP_DIR_SEND)
   @param[in] err_cb     Функция, вызываемая при ошибке передачи или NULL
   @param[in] cpl_cb     Функция, вызываемая при завершении передачи или NULL
   @return Указтель на состояние передачи или NULL, если нет свободного состояния
 ******************************************************************************/
void* lip_tftp_req_accept(const t_lip_tftp_recv_req_params* params,
                          t_lip_tftp_data_rcv_cb rcv_cb, t_lip_tftp_data_snd_cb snd_cb,
                          t_lip_tftp_err_cb err_cb, t_lip_tftp_cpl_cb cpl_cb) {
    unsigned i;
    int err;

    t_lip_tftp_file_state* state = f_get_free_file_state();
    if (state == NULL)
        return NULL;



    /************** заполняем инфу для передачи UDP  *****************/
    /* обнуляем UDP-информацию (на случай появления расширяющих полей в дальнейшем) */
    state->tx_info = *params->udp_info;
    state->tx_info.dst_port = params->udp_info->src_port;
    state->tx_info.data_size = 0;
    /* копируем сам адрес из параметра функции в поле в состоянии функции */
    memcpy(state->rem_addr, params->udp_info->src_addr, LIP_IPV4_ADDR_SIZE);
    /* адрес удаленной стороны указывает на поле внутри структуры состояния */
    state->tx_info.dst_addr = state->rem_addr;
    state->tx_info.src_addr = 0;

    /* выбираем TID - он же порт источника - выбираем случайно, но нужно
     * выбрать еще не прослушиваемый - на всякий случай делаем
     * LIP_TFPT_SELECT_TID_ATTEMPT_CNT попыток попасть на непросл. порт
     */
    for (err = LIP_ERR_UDP_PORT_ALREADY_LISTENED, i = 0;
            (err == LIP_ERR_UDP_PORT_ALREADY_LISTENED)
                    && (i < LIP_TFPT_SELECT_TID_ATTEMPT_CNT); i++) {
        /* в качесве порта источника выбирается случайный udp-порт из динамических */
        state->tx_info.src_port = f_tftp_gen_tid();
        /* начинаем прослушивать выбранный порт */
        err = lip_udp_port_listen(state->tx_info.src_port, f_lip_tftp_process_packet);
    }

    if (!err) {
        /* инициализируем параметры TFTP передачи */
        state->filename = 0;
#ifdef LIP_TFTP_SUPPORT_RECV
        state->rd_cb = rcv_cb;
#endif
#ifdef LIP_TFTP_SUPPORT_SEND
        state->snd_cb = snd_cb;
#endif
        state->err_cb = err_cb;
        state->cpl_cb = cpl_cb;
        state->block = 0;
        state->retrans = 0;
        state->last_time = lclock_get_ticks();
        state->dir = params->dir;
        state->block_size = params->block_size ? params->block_size : LIP_TFTP_DEFAULT_BLOCK_SIZE;
        state->flags = params->flags;
        state->fsize = params->file_size;

        state->tout = LTIMER_MS_TO_CLOCK_TICKS(LIP_TFTP_RETRANS_TIMEOUT_INIT);

#ifdef LIP_TFTP_SUPPORT_OPTS
        if ((state->block_size!=LIP_TFTP_DEFAULT_BLOCK_SIZE)
                || ((state->flags & _TFTP_FLAGS_SIZE_OP_PRESENT) && state->fsize)) {
            f_tftp_send_oack(state);
            state->state = e_TFTP_STATE_OACK_SEND;
        } else {
#endif
            if (state->dir == LIP_TFTP_DIR_RECV) {
#ifdef LIP_TFTP_SUPPORT_RECV
                f_tftp_send_ack(state);
                state->state = e_TFTP_STATE_ACK_DATA;
#else
                state = NULL;
#endif
            }

            if (state->dir == LIP_TFTP_DIR_SEND) {
#ifdef LIP_TFTP_SUPPORT_SEND
                err = f_process_send_block(state,0);
                if (err) {
                    f_tftp_send_err(&state->tx_info, err, "no send callback!");
                    state = NULL;
                } else {
                    state->state = e_TFTP_STATE_ACK_DATA;
                }
#else
                state = NULL;
#endif
            }
#ifdef LIP_TFTP_SUPPORT_OPTS
        }
#endif

    } else {
        f_tftp_send_err(&state->tx_info, LIP_TFTP_ERRCODE_NOTDEF, "server busy - don't have free ports");
        state = NULL;
    }
    return state;
}

#endif

#endif

/** @}*/



