/***************************************************************************//**
 @defgroup lip_tftp TFTP
 @ingroup  lip_lvl_app
 @brief Модуль стека @c lip, реализующий как серверную, так и клиентскую
  часть протокола TFTP.\n
  Зависит от: @ref lip_udp, @ref lip_rand


  Данный модуль реализует передачу файлов с использованием протокола
  TFTP в соответствии с @rfc{1350} с учетом пункта 4.2 @rfc{1123}.

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

  Режим клиента и сервера включаются с помощью макросов #LIP_USE_TFTP_CLIENT и
  #LIP_USE_TFTP_SERVER в файле конфигурации (@ref lip_conf). Также необходимо
  указать, какие направления передачи будут поддерживаться с помощью
  #LIP_TFTP_SUPPORT_SEND и #LIP_TFTP_SUPPORT_RECV.

  Режим клиента не поддерживает опций, определенных в @rfc{2347}, @rfc{2348},
    @rfc{2349}. В режиме сервера можно включить поддержку опций blksize и tsize
    с помощью #LIP_TFTP_SUPPORT_OPTS.

  @section lip_tftp_client Режим клиента

  В режиме клиента начало запроса на чтение или запись файла осуществляется
  с помощью lip_tftp_req_read() или lip_tftp_req_write().соответственно.

  Клиент поддерживает только режим передачи бинарных файлов #LIP_TFTP_MODE_OCTET.

  Чтение файла выполняется с помощью 3-х callback-функций, которые передаются
  в lip_tftp_req_read():
      - на каждый новый принятый пакет вызывается функция типа #t_lip_tftp_data_rcv_cb,
        в которой клиент должен обработать данные или вернуть ошибку.
        При возвращении ошибки другой стороне посылается покет с описанием ошибки
        и соединение закрывается (#t_lip_tftp_err_cb в этом случае не вызывается)
      - по успешному завершению передачи вызывается #t_lip_tftp_cpl_cb.
        Признаком успешного завершения является прием пакета меньшего размера, чем размер
        блока (сперва в этом случае вызывается #t_lip_tftp_data_rcv_cb для обработки,
        а затем #t_lip_tftp_cpl_cb).
      - по возникновению ошибки вызывается функция типа #t_lip_tftp_err_cb.
        При ее вызове передача уже остановлена. Возможными ошибками может быть
        прием пакета типа ERR, превышение кол-ва повторных передач или
        принудительный разрыв с помощью lip_tftp_abort().

  Передача осуществляется аналогичным образом с помощью callback-функций, указанных
  в lip_tftp_req_write().
     - При необходимости передать пакет вызывается #t_lip_tftp_data_snd_cb. Эта
       функция должна записать данные для передачи (не больше указанного размера)
       и изменить размер на реально записанный. Запись меньшего количества слов,
       чем запрашивалось является признаком конца передачи (по успешному завершению
       передачи этого пакета будет вызвана функция #t_lip_tftp_cpl_cb).
       Эта функция вызывается также при повторной передаче с флагом
        #LIP_TFTP_SND_FLAG_RETRANS. Программа обязана вернуть при этом те же самые
        данные того же самого размера, что и при предыдущем вызове!
     - #t_lip_tftp_cpl_cb - аналогично приему
     - #t_lip_tftp_err_cb - аналогично приему

  Указание #t_lip_tftp_data_rcv_cb и #t_lip_tftp_data_snd_cb является обязательным.
  В качестве #t_lip_tftp_cpl_cb и #t_lip_tftp_err_cb можно передавать нулевой
  указтель.


  @section lip_tftp_srv Режим сервера

  В режиме сервера для ожидания входных запросов на чтение/запись вызывается
  lip_tftp_listen_start() с указанием функции типа #t_lip_tftp_req_cb, которая
  будет вызываться при приеме действительного запроса.

  Прием запросов возможен для бинарных (#LIP_TFTP_MODE_OCTET) и текстовых файлов
  (#LIP_TFTP_MODE_ASCII).

  В функцию передаются параметры запроса в виде структуры типа #t_lip_tftp_recv_req_params.
  По этим параметрам программа внутри этой функции должна решить, принимаеть ли
  этот запрос. Для приема запроса нужно вызвать lip_tftp_req_accept(), передав
  в нее те же параметры и нужные callback-функции для осуществления передачи.
  Единственное изменение в параметрах, которое можно сделать - это установить
  размер файла для принятого запроса на чтение (направление #LIP_TFTP_DIR_SEND).
  Следует учитывать, что указатель на параметры действителен только внутри
  callback-функции.

  После подтверждения передача или прием файла осуществляется аналогично работе
  в режиме клиента.

  Если нужно отклонить запрос, то callback-функция должна вернуть код ошибки и
  записать строку с ее описанием. В случае если не было вызова lip_tftp_req_accept(),
  но при этом возвращен нулевой код (OK), то пакет запроса будет просто проигнорирован.

  @section lip_tftp_gen Общие положения


  Модуль может хранить только ограниченное количество состояний передач файлов,
  которое задется параметром #LIP_TFTP_REQUEST_CNT. Этот параметр задает максимальное
  общее количество активных трансферов файлов как на передачу, так и на прием,
  как в режиме сервера, так и в режиме клиента.

  Это состояние передачи инициализируется при вызове lip_tftp_req_read(),
  lip_tftp_req_write() или lip_tftp_req_accept() и непрозрачный указатель на него
  возвращается в качестве результата этих функций. При отсутствии свободного
  состояния будет возвращен нулевой указатель.

  При вызове callback-функций о ошибке или завершении передачи, состояние уже
  уничтожено, что позволяет начать новую передачу из этих функций, даже если
  #LIP_TFTP_REQUEST_CNT = 1. Исключение составляет завершение приема файла при
  определенном #LIP_TFTP_CPL_TIMEOUT, так как в этом случае информация о соединении
  еще сохраняется на протяжении #LIP_TFTP_CPL_TIMEOUT мс.

  Передачу файла можно отменить в любой момент с помощью lip_tftp_abort(), передав
  в нее указатель, возвращенный lip_tftp_req_read(), lip_tftp_req_write() или
  lip_tftp_req_accept(). При такой отмене также после останова передачи вызвается
  callback-функция типа #t_lip_tftp_err_cb.

  ID для передач генерируются с помощью генератора псевдослучайных чисел
     (см. @ref lip_rand) из диапазона динамических портов
  Начальный UDP-порт, на который шлется запрос, задается параметром функции

  Таймаут на повторную передачу увеличивается экспоненциально от
  #LIP_TFTP_RETRANS_TIMEOUT_INIT до #LIP_TFTP_RETRANS_TIMEOUT_MAX
  После завершения передачи протокол продолжает обрабатывать транзакцию
    еще в течении времени #LIP_TFTP_CPL_TIMEOUT (как рекомендуется в
    @rfc{1350} для возможности повторно послать подтверждение,
    если было потеряно)

  В начале добавляется задержка 0-50мс, для уменьшения вероятности одновременной
    посылки запроса сразу несколькими хостами.
 ******************************************************************************/

/***************************************************************************//**
 @addtogroup lip_tftp
 @{
 @file lip_tftp.h
 @brief Файл содержит определения функций и макросов для модуля @ref lip_tftp,
   которые могут быть использованы в приложении.
 @author Borisov Alexey <borisov@lcard.ru>
*******************************************************************************/

#ifndef LIP_TFTP_H_
#define LIP_TFTP_H_

#include "lip/lip_defs.h"
#include "lip/transport/udp/lip_udp.h"

/** Направление передачи по TFTP */
typedef enum {
    LIP_TFTP_DIR_RECV = 0, /**< Данные принимаются устройством от удаленного хоста*/
    LIP_TFTP_DIR_SEND = 1  /**< Устройство передает данные удаленному хосту */
} t_lip_tftp_req_direction;


/** Флаги, которые могут быть переданы в callback-функцию на передачу данных */
typedef enum {
    LIP_TFTP_SND_FLAG_RETRANS = 0x01 /**< Признак, что это запрос на повторную
                                          передачу блока */
} t_lip_tftp_snd_flags;

/** Порт TFTP-сервера по-умолчанию */
#define LIP_UDP_PORT_TFTP         69

/** коды ошибок протокола TFTP. Следует отметить, что эти коды ошибок отличаются
 * на 1 от определенных в RFC, чтобы иметь 0-ой код как признак отсутствия ошибки.
 * Код ошибки при передаче уменьшается на 1, а после приема входного пакета -
 * увеличивается. */
typedef enum {
    LIP_TFTP_ERRCODE_OK                  = 0, /**< Нет ошибки */
    LIP_TFTP_ERRCODE_NOTDEF              = 1, /**< Ошибка не определена */
    LIP_TFTP_ERRCODE_FILE_NOT_FOUND      = 2, /**< Запрашиваемый файл не найден */
    LIP_TFTP_ERRCODE_ACCESS_VIOLATION    = 3, /**< Ошибка доступа */
    LIP_TFTP_ERRCODE_DISK_FULL           = 4, /**< Диск заполнен */
    LIP_TFTP_ERRCODE_ILLEGAL_OP          = 5, /**< Запрещенная операция */
    LIP_TFTP_ERRCODE_UNKNOW_TID          = 6, /**< Неизвестный ID передачи */
    LIP_TFTP_ERRCODE_FILE_ALREADY_EXISTS = 7, /**< Файл уже существует */
    LIP_TFTP_ERRCODE_NO_SUCH_USER        = 8  /**< Нет пользователя */
} t_lip_tftp_err_code;

/** режим передачи для бинарных файлов */
#define LIP_TFTP_MODE_OCTET "octet"
/** режим передачи текстовых файлов */
#define LIP_TFTP_MODE_ASCII "netascii"


/** Параметры принятого запроса по TFTP */
typedef struct {
    t_lip_udp_info *udp_info; /**< Информация UDP-уровня */
    t_lip_tftp_req_direction dir; /**< Направление запрашиваемой передачи */
    const char *filename; /**< Указатель на строку с именем файла */
    const char *mode; /**<  Указатель на строку с режимом */
    uint32_t flags; /**< доп флаги */
    uint32_t file_size; /**< Размер файла (если 0 - не определен) */
    uint32_t block_size; /**< Размер блока (если 0 - не определен) */
} t_lip_tftp_recv_req_params;


/***************************************************************************//**
    Callback-функция, вызываемая при приходе очередного пакета по TFTP.
    Программа в этой функции должна обработать полностью данные и вертнуть
    управление по завершению обработки.
    @param[in] file_state  Указатель на непрозрачную структуру состояния передачи
                           (может использоваться для разделения разных файлов
                            и для вызова других функций)
    @param[in] data        Принятые данные (указатель действителен только на
                           момент выполнения функции)
    @param[in] size        Кол-во принятых данных
    @param[in] offset      Смещение в байтах относительно начала файла
    @param[out] err_str    В случае ошибки в этой переменной можно возвратить
                           указатель на строку с описанием.
    @return                Код ошибки
 ******************************************************************************/
typedef t_lip_tftp_err_code (*t_lip_tftp_data_rcv_cb)(void *file_state, const uint8_t *data,
                                                      int size, int offset,
                                                      const char **err_str);

/***************************************************************************//**
    Callback-функция, вызываемая при необходимости передать пакет данных
    удаленному хосту. Программа должна записать данные в указанный буфер размером
    size и изменить поле size, если записано меньше данных.
    Эта функция вызывается как при передаче следующего пакета, так и при повторной
    передаче (если истек таймаут на ожидание подтверждения). При повторной передаче
    необходимо всегда возвращать те же данных в том же количестве, что и при
    первой передаче данного пакета.

    @param[in] file_state  Указатель на непрозрачную структуру состояния передачи
                           (может использоваться для разделения разных файлов
                            и для вызова других функций)
    @param[out] data       Указатель на буфер в которых нужно записать данные
                           для передачи.
    @param[in,out] size    На входе - размер запрашиваемых данных, на выходе -
                           размер реальных данных (если меньше запрашиваемого
                           и нет ошибки - то это признак завершения передачи).
    @param[in] offset      Смещение в байтах относительно начала файла
    @param[in] flags       Флаги из #t_lip_tftp_snd_flags. Флаг #LIP_TFTP_SND_FLAG_RETRANS
                           указывает, что функция вызвана для поторной передачи
                           ранее запрашиваемого пакета, а не для нового.
    @param[out] err_str    В случае ошибки в этой переменной можно возвратить
                           указатель на строку с описанием.
    @return                Код ошибки
 ******************************************************************************/
typedef t_lip_tftp_err_code (*t_lip_tftp_data_snd_cb)(void *file_state, uint8_t *data, int *size, int offset,
                                                      int flags, const char **err_str);

/***************************************************************************//**
    Callback-функция, вызываемая при ошибке передачи. При вызове этой функции
    передача уже прекращена.

    @param[in] file_state  Указатель на непрозрачную структуру состояния передачи
                           (может использоваться для разделения разных файлов
                            и для вызова других функций)
    @param[in] err         Код ошибки. Если < 0 => одна из общих ошибок LIP,
                           если > 0 - то из #t_lip_tftp_err_code.
 ******************************************************************************/
typedef void (*t_lip_tftp_err_cb)(void *file_state, int err);

/***************************************************************************//**
    Callback-функция, вызываемая при завершении передачи файла.
    При вызове  этой функции передача уже прекращена (но в случае приема файла,
    состояние о соединении будет храниться еще #LIP_TFTP_CPL_TIMEOUT мс).

    @param[in] file_state  Указатель на непрозрачную структуру состояния передачи
                           (может использоваться для разделения разных файлов
                            и для вызова других функций)
    @param[out] err_str    В случае ошибки в этой переменной можно возвратить
                           указатель на строку с описанием.
    @return                Код ошибки
 ******************************************************************************/
typedef t_lip_tftp_err_code (*t_lip_tftp_cpl_cb)(void *file_state, const char **err_str);

/***************************************************************************//**
    Callback-функция, вызываемая при приеме сервером запроса на чтение или запись.
    Пользовательская программа, если хочет принять этот запрос, должна
    вызвать из этой callback-функции lip_tftp_req_accept().
    Если этого вызова не будет, то при возвращении кода ошибки отличного от 0,
    будет послано сообщение о ошибке,
    при возвращении нуля - запрос будет просто проигнорирован.

    @param[in] params      Параметры запроса.
                           При подтверждении запроса эти же параметры должны быть
                           переданы в lip_tftp_req_accept().
    @param[out] err_str    В случае ошибки в этой переменной можно возвратить
                           указатель на строку с описанием.
    @return                Код ошибки
 ******************************************************************************/
typedef t_lip_tftp_err_code (*t_lip_tftp_req_cb)(t_lip_tftp_recv_req_params *params, const char **err_str);


#ifdef LIP_USE_TFTP_CLIENT
    #ifdef LIP_TFTP_SUPPORT_RECV
        /** @brief Запрос на чтение файла из удаленного сервера */
        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);
    #endif

    #ifdef LIP_TFTP_SUPPORT_SEND
        /** @brief Запрос на передачу файла удаленному серверу */
        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);
    #endif
#endif

#ifdef LIP_USE_TFTP_SERVER
    /** @brief Разрешение прослушивания UDP-порта на приход TFTP-запросов */
    int lip_tftp_listen_start(uint16_t port, t_lip_tftp_req_cb cb);
    /** @brief Запрет прослушивания UDP-порта на приход TFTP-запросов */
    void lip_tftp_listen_stop(void);
    /** @brief Подтверждение принятого TFTP-запроса */
    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);

#endif

/** @brief Отмена указанной передачи */
void lip_tftp_abort(void *pstate, t_lip_tftp_err_code err, const char *err_str);
/** @brief Отмена всех незавершенных передач */
void lip_tftp_abort_all(t_lip_tftp_err_code err, const char *err_str);


#endif /* LIP_TFTP_H_ */

/** @}*/
