/**************************************************************************//**
 @defgroup lip_tcp TCP
 @ingroup  lip_lvl_transport
 @brief Модуль стека @c lip, реализующий протокол TCP.\n
  Зависит от: @ref lip_ip

 Данный файл содержит реализацию протокола TCP, а также сокет-подобного
    интерфейса, в соответствии с @rfc{793}, с учетом исправлений из @rfc{1122}.

Особенности реализации протокола
=================================

Для протокола TCP реализованы следующие алгоритмы:
  - Выбор момента передачи данных осуществляется с использованием алгоритмов
    Нейгла (Nagle), который можно отключить с помощью опции #TCP_NODELAY,
    и алгоритма избежания SWS ("Silly Window Syndrome) согласно
    @rfc{1122} 4.2.3.4 page 98-100
  - отложенная посылка подтверждения (delayed ACK) на время от 1 до #LIP_TCP_DELAYED_ACK_TIME
    мс, но с немедленным подтверждением второго сегментом
    (@rfc{1122} 4.2.3.2 page 96, @rfc{5681} 4.2 page 11-12)
  - Вычисление таймаута на повторную передачу в соответствии с @rfc{6298}
  - Реализация механизма keepalive (при включении с помощью опции #SO_KEEPALIVE)
    для проверки "живо" ли соединение по которому нет передачи
  - Проверка нулевого окна (@rfc{1122} 4.2.2.17 page 92)
  - Slow start/congestion avoidance в соответствии с @rfc{5681} 3.1 с выходом
    из idle в как в пункте 4.1
  - Fast retransmit/fast recovery (@rfc{5681} 3.2) с использованием алгоритма
      NewReno (@rfc{6582}), а так же Limited Transmit (@rfc{3042})
  - Реализация опций Window scale, а также Timestamp для RTTM и PAWS (@rfc{1323})
  - Прием не подряд идущих сегментов и генерация SACK-опции для
      их подтверждения (@rfc{2018})
  - Алгоритм избежания SWS для окна на прием (@rfc{1122} 4.2.3.3. page 97-98)
      при обмене через буфер (при заданиях размер буфера и есть размер задания,
      пользователь должен добавлять задания приемлемого размера)
  - ICMP сообщение SourceQuench не обрабатывается и не должно в соответствии с
      @rfc{6633} (переопределяет пункты 3.2.2.3 и 3.2.3.9 @rfc{1122})

Нереализованные в данный момент возможности:
  - реализация D-SACK (@rfc{2883}), использование SACK при определении DUP-ACK,
      алгоритм восстановления @rfc{6675}
  - Urgent Pointer (@rfc{1122} 4.2.2.4)
  - Возможно сделать флаг, когда нет возможности передавать, чтобы не проверять
      снова, пока ситуация не изменится (пришел ACK, добавлены данные и т.д.)
      и уменьшить время lip_pull()
  - Обработка ICMP сообщений Destination Unreachable (@rfc{1122} page 103 4.2.3.9)
      с учетом условий из @rfc{5927}
  - Проверка QOS, Precedence c IP-уровня
  - Сокет должен находится в TimeWait 2MSL --- можно сделать просто список параметров
      сокетов в этом состоянии, чтобы не занимать ресурсы сокета.
  - Случайный выбор порта при активном соединении, как рекомендуется в @rfc{5927}
  - Реализация специальной очереди прослушивания порта, возвращающей сокет
    при завершении открытия удаленной стороной (чтобы быть меньше подверженым
    проблеме недооткрытых соединений, для чего приходится делать несколько
    полноценных прослушивающих сокетов в параллель)

Дополнительные возможности, которые может быть можно было бы рассмотреть в будущем:
  - два порога повторных передач (по первому --- запрос ip уровню, по второму --- разрыв).
      Сейчас только второй
  - MTU Path Discovery (@rfc{1191}, @rfc{4821}) с учетом пункта 7.2 @rfc{5927}
  - ECN (@rfc{3168})
  - опции маршрутизации с IP-уровня




 Интерфейс с прикладным уровнем
==============================================

 Данный модуль реализует сокет-подобный интерфейс для прикладного уровня.
 При этом сокет соответствует описанному сокету в @rfc{793} (и отличается от
    BSD-сокетов). В частности прикладной уровень должен сам отслеживать изменения
    состояния при открытии и закрытии соединения, а также организовывать
    очередь для приема соединений при прослушивании порта.
    Однако сокеты скрывают от прикладного уровня процесс обмена данными и реализуют
    внутри себя все необходимые алгоритмы.

 Для каждого соединения (которое может существовать параллельно) необходимо
    создать свой сокет с помощью функции lsock_create(), которая возвращает 
    идентификатор сокета, который будет использован в остальных функциях.
    Одновременно может быть создано до #LIP_TCP_USER_SOCKET_CNT пользовательских
    сокетов --- сокетов с котороми приложение работает напрямую посредством данных
    функций (без учета количества TCP-сокетов, используемых встроенными модулями
    стека).

 Открытие соединения
 -------------------

 Сокет после создания находится в состояние #LIP_TCP_SOCK_STATE_CLOSED и не соответствует
    ни одному открытому соединению. Перед открытием соединения обычно выполняются
    необходимые настройки сокета (например установка буферов для режима обмена
    через буфер). Открытие соединения может быть двух типов:

    - Пассивное (со стороны сервера). Сокет переводится в режим прослушивания
      определенного локального TCP-порта с помощью lsock_listen(). При этом сокет
      принимает входящие соединения на этот порт от удаленного хоста. Для возможности
      параллельного открытия нескольких соединений, приложение может создать
      несколько сокетов и перевести их все в режим прослушивания через lsock_listen().
      При этом при входящем соединении будет выбран первый свободный слушающий
      данный порт сокет для инициирования процесса открытия соединения. 
      Можно явно задать, чтобы сокет принимал соединения только от удаленного 
      хоста с определенным портом и/или определенным адресом, вызвав перед 
      lsock_listen() функции lsock_set_remote_port() и lsock_set_remote_ip() 
      соответственно.
    - Активное (со стороны клиента). Для включения этой возможности должен быть
      определен макрос #LIP_TCP_USE_ACTIVE_CONNECT. При активном соединении
      выполняется попытка подключиться к удаленному хосту, который должен
      прослушивать заданный порт. Для этого сперва надо задать TCP-порт и
      адрес удаленного хоста, вызвав lsock_set_remote_port() и
      lsock_set_remote_ip() соответственно, а затем для установления соединения
      вызвать lsock_connect().

  Независимо от типа соединения факт установки соединения определяется тем, что
  сокет переходит в состояние #LIP_TCP_SOCK_STATE_ESTABLISHED. При активном открытии
  сокет также может перейти в состояние #LIP_TCP_SOCK_STATE_CLOSED, если соединение
  установить не удалось (исчерпано количество повторов запроса соединения или
  принят RST).

  Закрытие соединения
  --------------------
  
  Корректное закрытие соединения выполняется с помощью lsock_shutdown() и ожидания
  перехода сокета в состояние #LIP_TCP_SOCK_STATE_CLOSED, в этом случае выполняется
  обмен всеми необходимыми сегментами для закрытия соединения. При этом после
  вызова lsock_shutdown() данные могут приниматься и приложение может их прочитать
  и, более того, должно их читать, чтобы было место в буфере сокета для приема запроса
  на закрытие от другой стороны, когда он поступит. Если данные на прием больше 
  не интересуют, то можно использовать lsock_set_recv_drop_mode().
  В случае необходимости аварийного  закрытия можно вызвать
  lsock_abort(), если необходимо немедленно перевести сокет в состояние
  #LIP_TCP_SOCK_STATE_CLOSED и использовать этот сокет для нового соединения
  (так как корректное закрытие может длиться произвольное время).

  Также для удобства закрытия реализована функция lsock_close(), которая
  объединяет действие трех вышеперечисленных. По вызову функции соединение
  закрывается как на прием, так и на передачу (т.е. действия функций
  lsock_set_recv_drop_mode() и lsock_shutdown()). Кроме того, в функцию передается
  таймаут, за который соединение должно коректно закрыться. Если за заданный таймаут
  корректно закрыть соединение не удалось, то оно разрывается аварийно, аналогично
  вызову lsock_abort().

  Все вышеперечисленные функции не уничтожают сам сокет, а только соединение,
  и после перехода сокета в состояние #LIP_TCP_SOCK_STATE_CLOSED он может быть
  использован для открытия нового соединения.
  Для освобождения ресурсов сокета используется lsock_destroy().

  Если сокет находился в состоянии #LIP_TCP_SOCK_STATE_ESTABLISHED, а удаленный хост
  корректно закрыл соединение, то сокет переходит в состояние #LIP_TCP_SOCK_STATE_CLOSING
  (в этом состоянии можно посылать данные, но новые данные приходить уже не могут)
  до того момента, как локальное приложение само ни закроет соединение.

  Кроме того, стек может сам оборвать существующее соединение, если:
    - выполнено #LIP_TCP_RETRANS_MAX_CNT попыток повторной передачи сегмента
      по таймеру без подтверждения сегмента
    - удаленный хост аварийно закрыл соединение (послал RST)
    - от удаленного хоста приняты сегменты, не соответствующие корректной работе
      TCP и не объясняющиеся дубликатами, пропажей или задержками пакетов.
    - локальный IP-адрес, который использовал сокет, более недействителен (изменен)
    - стек полностью закрыт через lip_close()

  Передача данных
  ----------------
  Для передачи данных существует два разных режима работы:
    - передача через буфер сокета (включается определением #LIP_TCP_FIFO_MODE_ENABLE)
    - передача через задания на обмен (включается определением #LIP_TCP_DD_MODE_ENABLE)

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

  Кроме того на прием существует режим, в котором все принятые данные молча
  выбрасываются, который можно установить через lsock_set_recv_drop_mode().


### Обмен данными через буфер сокета ###

Данный режим во многом схож со стандартным интерфейсом BSD-сокетов. Данный режим
прост в использовании, однако недостатком является необходимость дополнительного
копирования пользовательских данных в буфер сокета.

Одной из особенностей реализации этого режима является то, что
пользователь сам создает буфер для приема и/или передачи и назначает
его сокету с помощью lsock_set_recv_fifo() и lsock_set_send_fifo().
Это назначение должно быть выполнено после создания
сокета до установления соединения.

В простейшем случае после этого управление буфером переходит сокету.
Для передачи данных пользователь кладет данные в буфер сокета через lsock_send()
и уже в дальнейшем стек сам определяет моменты передачи в lip_pull(). Когда
данные подтверждены другой стороной, эта часть буфера помечается свободной.
Если при вызове lsock_send() в буфере сокета нет достаточно места для всех данных, 
то будет записано только столько данных, на сколько хватит места (и это количество 
будет возвращено как результат lsock_send()) без блокировки программы.
Узнать, сколько есть свободного места в буфере, можно с помощью lsock_send_rdy_size().

Принятые данные сокет складывает в установленный буфер на прием и они могут быть
прочитаны с помощью lsock_recv(). Аналогично, lsock_recv() возвращает не больше
данных, чем уже есть в буфере и не блокирует выполнение. Узнать количество
данных в буфере, готовых для приема, можно с помощью lsock_recv_rdy_size().

В некоторых случаях  в этом режиме можно избежать копирования данных, если
программа прикладного уровня будет подготавливать данные для передачи и/или
обрабатывать принятые данные непосредственно в буфере сокета.
Так как программа сама устанавливает буфер сокета, то у нее
есть непосредственный доступ в буфер (но программе нужно самой отслеживать
границы буфера). Для передачи в этом режиме программа может подготовить в
буфере, начиная с позиции, полученной lsock_send_fifo_get_pos(),
данные для передачи (не больше чем размер свободных данных, полученный
lsock_send_rdy_size()) и вызвать lsock_send_fifo_update_pos(), чтобы пометить
эти данные, как готовые к передаче.
Для приема, аналогично, программа может обработать данные в буфере на прием,
начиная с позиции, полученной lsock_recv_fifo_get_pos(), размером не больше
принятых данных, полученном с помощью lsock_recv_rdy_size(),  а затем вызвать
lsock_recv_fifo_update_pos(), чтобы пометить эти данные как прочитанные.



### Обмен данными через задания на обмен ###

В этом режиме сокету не назначается никакого постоянного буфера. Вместо этого
у сокета есть очередь заданий на прием и/или очередь заданий на передачу.
Задание представляет из себя буфер для передачи или приема данных и его размер.
Задания добавляются с помощью lsock_send_start() и lsock_recv_start(), а
максимальное количество заданий на прием и передачу определяется парметрами
конфигурации #LIP_CFG_TCP_SOCK_RX_DD_CNT и #LIP_CFG_TCP_SOCK_TX_DD_CNT соответственно.
По завершению задания вызывается пользовательская функция.

Переход в данный режим приема или передачи выполняется после первого вызова
lsock_recv_start() и lsock_send_start() соответственно.

Так как при приеме часто заранее неизвестно, сколько другая сторона передаст
данных, то предоставляется способ обработать часть принятых данных до завершения 
всего задания. Для этого можно использовать функцию lsock_recv_rdy_size() (работает
в обоих режимах), которая в режиме обмена через задания данная вернет количество
принятых данных для текущего незавершенного задания на прием (соответственно
эти данные уже можно использовать). По завершению задания это значение сбрасывается
в 0, вызывается пользовательская функция и идет переход к следующему заданию.

Следует также отметить, что при разрыве соединения, для всех незавершенных
заданий вызываются пользовательские функции и они удаляются из очереди.

******************************************************************************/

/***************************************************************************//**
 @addtogroup lip_tcp
 @{
 @file lip_tcp.h
 @brief Файл содержит определения функций и макросов для работы с TCP-сокетами

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

#ifndef LIP_TCP_H_
#define LIP_TCP_H_


#include "lip_tcp_cfg_defs.h"
#include "lip/lip_defs.h"
#include "lip/net/lip_ip.h"
#include "lip_sock.h"
#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
#include "lip/misc/lip_misc_data_copy.h"
#endif




/** Тип для определения идентификатора сокета. Возвращается lsock_init() и
 *  используется во всех функциях для работы с сокетами */
typedef int t_lsock_id;

/** Идентификатор соекта, соответствующий ошибке при создании сокета */
#define LIP_TCP_INVALID_SOCKET_ID  -1

/** Номер порта, соответствующий неустановленному или недействительному порту TCP */
#define LIP_TCP_INVALID_PORT 0


/** Состояние соединения TCP */
typedef enum {
    LIP_TCP_SOCK_STATE_LISTEN        = 1, /**< Пассивное ожидание подключения от
                                          удаленного хоста */
    LIP_TCP_SOCK_STATE_SYN_SENT      = 2, /**< Был передан SYN при активном установлении
                                          соединения и ожидается ответный запрос */
    LIP_TCP_SOCK_STATE_SYN_RECEIVED  = 3, /**< Был принят запрос на соединение и послан свой
                                          запрос, на который ожидается подтверждение */
    LIP_TCP_SOCK_STATE_ESTABLISHED   = 4, /**< Установленное соединение для обмена данными
                                          как на прием, так и на передачу */
    LIP_TCP_SOCK_STATE_FIN_WAIT1     = 5, /**< Ожидание запроса на закрытие от другой стороны
                                          и подтверждения своего посланного запроса на закрытие */
    LIP_TCP_SOCK_STATE_FIN_WAIT2     = 6, /**< Ожидание запроса на закрытие от другой стороны,
                                          когда свой запрос был уже подтвержден */
    LIP_TCP_SOCK_STATE_CLOSE_WAIT    = 7, /**< Был принят запрос на закрытие от другой
                                          стороны, но еще не была вызвана функция
                                          закрытия соединения со стороны программы
                                          на локальном хосте */
    LIP_TCP_SOCK_STATE_CLOSING       = 8, /**< Ожидание подтверждения посланного запроса
                                          на закрытие, которое было до посылки
                                          подтверждения запроса на закрытие от
                                          удаленного хоста */
    LIP_TCP_SOCK_STATE_LAST_ACK      = 9, /**< Ожидание подтверждения посланного запроса
                                          на закрытие в котором было и подтверждение
                                          удаленного закрытия */
    LIP_TCP_SOCK_STATE_TIME_WAIT     = 10, /**< Ожидание времени, чтобы быть уверенным, что
                                           удаленная сторона приняла подтверждение
                                           своего запроса на закрытие соединения */
    LIP_TCP_SOCK_STATE_CLOSED        = 0,  /**< Закрытое соединение. Сокет не соответствует
                                           ни одному соединению и может быть использован
                                           для установки нового */
    LIP_TCP_SOCK_STATE_NOT_IN_USE    = 0xFF /**< Соответствует неинициализированному
                                            сокету, который не может быть использован
                                            вышестоящими протоколами прикладного
                                            уровня */
} t_lip_tcp_sock_state;




#ifndef LIP_TCP_WINSCALE_MIN
    #define LIP_TCP_WINSCALE_MIN  4
#endif

#define LIP_TCP_MIN_WIN (1UL << LIP_TCP_WINSCALE_MIN)

/** @brief Минимальный размер буфера на прием для сокета

    Вспомогательный макрос, который определяет минимальный размер приемного буфера
    для сокета, необходимый  для корректной работы, при условии того, что максимальный
    размер данных в одном TCP пакете не превышает заданное количество байт.
    Макрос учитывает алгоритмы,  используемые для объявления окна. Использование
    буфера меньше может привести к тому, что окно будет постоянно заполнено,
    если из буфера вычитываются только пакеты целиком (а не по частям) */
#define LIP_TCP_RX_BUF_MIN(max_packet) (2*((max_packet + LIP_TCP_MIN_WIN - 1) & ~(LIP_TCP_MIN_WIN - 1)))

#if LIP_TCP_DD_MODE_ENABLE
    struct st_lip_tcp_dd;

    /** Статус завершения задания на обмен */
    typedef enum {
        LIP_TCP_DD_STATUS_DONE           = 0, /**< Задание успешно завершено */
        LIP_TCP_DD_STATUS_CON_CLOSED     = 1, /**< Задание завершено из-за закрытия соединения */
        LIP_TCP_DD_STATUS_CON_ABORTED    = 2  /**< Задания завершено из-за аварийного разрыва соединения */
    } t_lip_tcp_dd_status;

    /** Тип функции, вызываемой при завершении задания на обмен.
        @param[in] sock_id      Идентификатор сокета, задание для которого было завершено
        @param[in] pdd          Структура, описывающая завершенное задание
        @param[in] status       Статус завершения задания  */
    typedef void (*t_lip_tcp_dd_cb)(t_lsock_id sock_id, const struct st_lip_tcp_dd *pdd, t_lip_tcp_dd_status status);



    /** Структура, описывающая одно задание на обмен данных по TCP */
    typedef struct st_lip_tcp_dd {
        uint8_t* buf;                /**< Адрес буфера для обмена */
        t_lip_tcp_dd_size length;    /**< Размер буфера в байтах */
        t_lip_tcp_dd_size trans_cnt; /**< Количество реально переданных/принятых байт */
        t_lip_tcp_dd_cb cb;          /**< Функция, которая будет вызвана по завершению
                                          задания на обмен */
    } t_lip_tcp_dd;
#endif





/** @brief Создание сокета */
t_lsock_id lsock_create(void);
/** @brief Получение состояние сокета */
t_lip_tcp_sock_state lsock_state(t_lsock_id sock_id);

/** @brief Явная установка TCP-порта удаленного хоста для сокета */
t_lip_errs lsock_set_remote_port(t_lsock_id sock_id, uint16_t port);
/** @brief Явная установка адреса удаленного хоста для сокета */
t_lip_errs lsock_set_remote_ip(t_lsock_id sock_id, const t_lip_ip_addr *addr);
/** @brief Получение номера TCP-порта удаленного хоста */
t_lip_errs lsock_get_remote_port(t_lsock_id sock_id, uint16_t *port);
/** @brief Получение адреса удаленного хоста для сокета */
t_lip_errs lsock_get_remote_ip(t_lsock_id sock_id, t_lip_ip_addr *addr);


/** @brief Перевод сокета в режим прослушивания порта на предмет установления удаленных соединений */
t_lip_errs lsock_listen(t_lsock_id sock_id, uint16_t port);

/** @brief Запрос на закрытие соединения, соответствующего сокету */
t_lip_errs lsock_shutdown(t_lsock_id sock_id);
/** @brief Немедленный разрыв соединения, соответствующего сокету */
t_lip_errs lsock_abort(t_lsock_id sock_id);
/** @brief Закрытие соединения на чтение и запись с таймаутом на завершения операции */
t_lip_errs lsock_close(t_lsock_id sock_id, uint32_t tout);
/** @brief Закрытие соединения и очистка ресурсов сокета по завершению закрытия */
t_lip_errs lsock_destroy(t_lsock_id sock_id, uint32_t tout);



/** @brief Включение режима на прием, когда все входящие данные отбрасываются */
t_lip_errs lsock_set_recv_drop_mode(t_lsock_id sock_id);

/** @brief Установка опции сокета */
t_lip_errs lsock_set_opt(t_lsock_id sock_id, int level, int opt, const void *optval, socklen_t optlen);

t_lip_errs lsock_get_rx_eth_port(t_lsock_id sock_id, uint8_t *port);



#ifdef LIP_TCP_USE_ACTIVE_CONNECT
    /** @brief Активное подключение к удаленному хосту */
    t_lip_errs lsock_connect(t_lsock_id sock_id);
#endif


#if LIP_TCP_FIFO_MODE_ENABLE
    /** @brief Установка очереди для приема данных для сокета */
    t_lip_errs lsock_set_recv_fifo(t_lsock_id sock_id, uint8_t* buf, int size);
    /** @brief Установка очереди для передачи данных для сокета */
    t_lip_errs lsock_set_send_fifo(t_lsock_id sock_id, uint8_t* buf, int size);
    /** @brief Прием данных из очереди сокета */
    int lsock_recv(t_lsock_id sock_id, uint8_t* buf, int size);
    /** @brief Передача данных в очередь сокета */
    int lsock_send(t_lsock_id sock_id, const uint8_t* buf, int size);


    /** @brief Получение позиции принятых, но не прочитанных данных в буфере сокета */
    t_lip_errs lsock_recv_fifo_get_pos(t_lsock_id sock_id, int* pos);
    /** @brief Увеличение позиции принятых, но не прочитанных данных в буфере сокета */
    t_lip_errs lsock_recv_fifo_update_pos(t_lsock_id sock_id, int size);


    /** @brief Получение позиции для помещения новых данных на передачу в буфере сокета */
    t_lip_errs lsock_send_fifo_get_pos(t_lsock_id sock_id, int* pos);
    /** @brief Увеличение позиции для помещения новых данных на передачу в буфере сокета */
    t_lip_errs lsock_send_fifo_update_pos(t_lsock_id sock_id, int size);



    /** @brief Получить количество данных, которое можно передать */
    int lsock_send_rdy_size(t_lsock_id sock_id);
#endif

/** @brief Получить количество принятых, но не прочитанных данных */
int lsock_recv_rdy_size(t_lsock_id sock_id);

#if LIP_TCP_DD_MODE_ENABLE
    /** @brief Добавление задания на передачу данных в очередь заданий сокета */
    t_lip_errs lsock_send_start(t_lsock_id sock_id, const uint8_t* buf, int size, t_lip_tcp_dd_cb cb);
    /** @brief Получить количество незавершенных заданий на передачу */
    int lsock_send_dd_in_progress(t_lsock_id sock_id);

    /** @brief Добавление задания на прием данных в очередь заданий сокета */
    t_lip_errs lsock_recv_start(t_lsock_id sock_id, uint8_t* buf, int size, t_lip_tcp_dd_cb cb);
    /** @brief Получить количество незавершенных заданий на прием */
    int lsock_recv_dd_in_progress(t_lsock_id sock_id);
    /** @brief Получить состояние активного задания на прием */
    t_lip_errs lsock_recv_cur_dd(t_lsock_id sock_id, t_lip_tcp_dd *dd);
#endif

#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
    /** @brief Установка пользовательской функции для копирования принятых данных из пакетв в буфер сокета или задания */
    t_lip_errs lsock_set_recv_data_copy_func(t_lsock_id sock_id, t_lip_data_copy_func func, void *func_ctx);
    /** @brief Установка пользовательской функции для копирования данных в пакет на передачу из буфера сокета или задания */
    t_lip_errs lsock_set_send_data_copy_func(t_lsock_id sock_id, t_lip_data_copy_func func, void *func_ctx);
#endif

#ifdef LIP_TCP_CB_GET_ISS
    /** @brief Получение начального значение номера сегмента
      
        Пользовательская функция, которая вызывается при инициализации стека,
        если определен макрос #LIP_TCP_CB_GET_ISS, которая должна возвратить
        начальный номер сегмента, на основе которого будут формироваться
        начальные номера сегментов для создаваемых в дальнейшем соединений 
        @return Начальный номер сегмента */
    uint32_t lip_tcp_cb_get_iss(void);
#endif

#endif /* LIP_TCP_H_ */

/** @}*/
