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

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

#include "lip_tcp_cfg_defs.h"
#if LIP_TCP_ENABLE

#include "lip/lip_log.h"
#include "lip_tcp_private.h"
#include "lcspec.h"
#include "ltimer.h"
#include "lip_ll_port_features.h"
#include "lip/app/opcua/lip_opcua_cfg_defs.h"
#include "lip/app/http/lip_http_server_cfg_defs.h"
#include "lip/examples/tcp_cntr/lip_sample_tcp_cntr_cfg_defs.h"
#include <string.h>


#ifndef LIP_MB_TCP_SERVER_SOCKET_CNT
    #define LIP_MB_TCP_SERVER_SOCKET_CNT 0
#endif
#if LIP_HTTP_SERVER_ENABLE
    #define LIP_HTTP_SERVER_SOCKET_CNT  LIP_HTTP_SERVER_CONNECTIONS_CNT
#else
    #define LIP_HTTP_SERVER_SOCKET_CNT  0
#endif
#if LIP_EXAMPLE_TCP_ENABLE
    #define LIP_EXAMPLE_TCP_SOCKET_CNT  1
#else
    #define LIP_EXAMPLE_TCP_SOCKET_CNT  0
#endif

/* общее кол-во соектов определяется как заданное количество пользовательских соектов +
   кол-во сокетов, используемое встроенными модулями lip */
#define LIP_TCP_SOCKET_CNT (LIP_TCP_USER_SOCKET_CNT + \
                            LIP_MB_TCP_SERVER_SOCKET_CNT + \
                            LIP_HTTP_SERVER_SOCKET_CNT + \
                            LIP_OPCUA_SERVER_SOCKET_CNT + \
                            LIP_EXAMPLE_TCP_SOCKET_CNT)



/****************** Установка неопределенных опциональных параметров **********/
/** @addtogroup lip_conf_tcp
 *  @{ */
#if (LIP_TCP_DELAYED_ACK_TIME > 500)
    #error "delayed ack must be < 500ms - rfc1122 4.2.3.2 page 96"
#endif

#ifndef LIP_TCP_SWS_PUSH_TOUT
    /** Опционально. Переопределяет таймаут в мс для передачи сегмента без учета SWS
        (rfc1122 p100) - [0.1 - 1] c. По-умолчанию --- 1с */
    #define LIP_TCP_SWS_PUSH_TOUT 1000
#endif


#ifndef LIP_TCP_RCV_NONCONT_BLOCKS_MAX
    /** Опционально. Количество пропусков (дыр) в принимаемых сегментах,
     *  которое можно обработать, сохраняя идущие не подряд данные */
    #define LIP_TCP_RCV_NONCONT_BLOCKS_MAX  5
#endif


#ifndef LIP_TCP_RTO_INIT
    /** Опционально. Начальное значение в мс таймаута повторной передачи
        (используется, пока не измерено время отклика (RTT) */
    #define LIP_TCP_RTO_INIT          1000
#else
    #warning "use nondefault rto times is not recommended"
#endif

#ifndef LIP_TCP_RTO_SYN_BACKOFF
    /** Опционально. Начальное значение в мс таймаута повторной передачи,
     *  который устанавливается при в случае потери и последующей повторной
     *  передаче запроса на установление соединения (@rfc{6298}, 5-7, p.5) */
    #define LIP_TCP_RTO_SYN_BACKOFF  3000
#else
    #warning "use nondefault rto times is not recommended"
#endif

#ifndef LIP_TCP_RTO_MIN
    /** Опционально. Минимальное время в мс таймаута для повторной передачи данных */
    #define LIP_TCP_RTO_MIN           1000
#endif

#ifndef LIP_TCP_RTO_MAX
    /** Опционально. Максимальное время (мс) истечения таймера для повторной
     *  передачи сегментов */
    #define LIP_TCP_RTO_MAX           60000
#endif

/** @} */


/* Winscale не должно превышать 14, чтобы однозначно отличить старые пакеты от новых
 * (rfc1323 p.11) */
#define LIP_TCP_WINSCALE_MAX 14







#define LIP_TCP_HDR_SIZE_MAX   60


#ifdef LIP_TCP_USE_TIMESTAMP
    #if (LCLOCK_TICKS_PER_SECOND > 1000) || (LCLOCK_TICKS_PER_SECOND < 1)
        #error "timestamp clock must be in the range 1 ms to 1 sec per tick (rfc1323 p21)"
    #endif
#endif


/* количество сегментов, которое может быть передано больше cwnd
 * по сегменту на дублированный ack до входа в fast_retransmit */
#define LIP_TCP_LIM_TRANS_SEG_CNT  2



/* время, через которое TS.Recent считается недействительным - 24 дня */
#define LIP_TCP_PAWS_INVALID_TIME   (24*24*3600*1000UL)


#define LIP_TCP_ZERO_WIND_PROB_INC_CNT  12

//увеличиваем iss на заданное число при каждом соединении
#define LIP_TCP_ISS_INC_BY_CON  64000

#define LIP_TCP_DUP_ACK_THRESHOLD 3

#define LIP_TCP_DEFAULT_MSS  536


#define SOCK_IS_NON_SYNCRONIZED(state) ((state == LIP_TCP_SOCK_STATE_SYN_RECEIVED) \
                                    || (state == LIP_TCP_SOCK_STATE_SYN_SENT)\
                                    || (state == LIP_TCP_SOCK_STATE_LISTEN))


#define SOCK_INC_RETR_CNT(cnt) (cnt = cnt==(LIP_TCP_RETRANS_MAX_SEG-1) ? 0 : cnt+1)


#define SOCK_VALID(sock) (((sock)>=0) && ((sock) < LIP_TCP_SOCKET_CNT) && \
    (SOCK_STRUCT(sock)->state != LIP_TCP_SOCK_STATE_NOT_IN_USE))


#define SOCK_CHECK_ID(id) (SOCK_VALID(id) ? LIP_ERR_SUCCESS : LIP_ERR_TCP_SOCK_INVALID_ID)

#define SOCK_STRUCT(id) (&f_tcp_sock_list[(id)])
#define SOCK_ID_BY_IDX(i)  (i)

/* сложение с отслеживанием переполнения */
#define UINT32_ADD_SAT(val, add_val)  (((uint32_t)((val) + (add_val)) > (uint32_t)val) ? \
                                       (uint32_t)((val) + (add_val)) : (uint32_t)(-1))



//стандартные флаги TCP
#define LIP_TCP_FLAGS_FIN          0x01
#define LIP_TCP_FLAGS_SYN          0x02
#define LIP_TCP_FLAGS_RST          0x04
#define LIP_TCP_FLAGS_PSH          0x08
#define LIP_TCP_FLAGS_ACK          0x10
#define LIP_TCP_FLAGS_URG          0x20

//маска для стандартных флагов TCP
#define LIP_TCP_FLAGSMSK           0x3F

/* идет повторная передача=>нет обновлений номеров последовательностей */
#define LIP_TCP_SENDFLAGS_RETRANS     0x00010000
 /* посылается keep-alive пакет: используется seq_num - 1 и не сохраняется в очереди на повторную передачу */
#define LIP_TCP_SENDFLAGS_KEEPALIVE   0x00020000


/* коды опций tcp-пакета */
#define LIP_TCP_OPT_CODE_END       0x0
#define LIP_TCP_OPT_CODE_NOP       0x1
#define LIP_TCP_OPT_CODE_MSS       0x2
#define LIP_TCP_OPT_CODE_WS        0x3
#define LIP_TCP_OPT_CODE_SACK_PERM 0x4
#define LIP_TCP_OPT_CODE_SACK      0x5
#define LIP_TCP_OPT_CODE_TIMESTAMP 0x8


#define LIP_TCP_OPT_LEN_MSS        0x4
#define LIP_TCP_OPT_LEN_WS         0x3
#define LIP_TCP_OPT_LEN_SACK_PERM  0x2
#define LIP_TCP_OPT_LEN_TIMESTAMP  0xA

/* общий размер опций в пакете на передачу, включая padding */
#define LIP_TCP_OPT_TIMESTAMP_TOTAL_LEN 12


/* флаги состояния сокета */
typedef enum {
    /* признак, что соединение было открыто активно (через lsock_connect()) */
    SOCK_FLAGS_ACTIVE_OPEN           = 0x00000001,
    /* признак, что сокет был помечен как не используемый через lsock_destroy()
       и его можно будет поместить в свободные как только соединение будет закрыто */
    SOCK_FLAGS_CAN_FREE              = 0x00000002,
    /* признак, что нужно послать запрос на завершение соединения (FIN) как только
     * будут переданы все данные */
    SOCK_FLAGS_NEED_FIN              = 0x00000004,
    /* признак, что запрос на завершения (FIN) уже был послан */
    SOCK_FLAGS_FIN_SENT              = 0x00000008,
    /* признак, что необходимо немедленно послать подтверждение */
    SOCK_FLAGS_NEED_ACK              = 0x00000010,
    /* признак, что по таймауту нужно послать delayed ack */
    SOCK_FLAGS_DELAYED_ACK           = 0x00000020,
    /* признак, что приняли сегмент для подтверждения, но не послали ack.
       используются, чтобы послать немедленно ack на второй сегмент (rfc5681 6) */
    SOCK_FLAGS_RECV_UNACK_SEG        = 0x00000040,
    /* признак, что соединение было аварийно завершено (по RST, а не через
     * обмен FIN) */
    SOCK_FLAGS_CON_ABORTED           = 0x00000080,
    /* признак, что принятые данные нужно отбросить, а не передавать наверх,
     * а окно можно объявить максимальным */
    SOCK_FLAGS_RX_DROP_MODE          = 0x00000100,
    /* признак, что для соединения измерили хотя бы один раз время отклика (RTT) */
    SOCK_FLAGS_RTT_SET               = 0x00000200,
    /* признак, что был повтор сегмента с SYN (для определения начального cwnd и RTO) */
    SOCK_FLAGS_SYN_RETR              = 0x00000400,
    /* признак, что вошли в состояние FastRecovery (после FastRetransmit) */
    SOCK_FLAGS_FAST_RECOVERY         = 0x00000800,
    /* признак, что не было активности в течении RTO и cwnd было уменьшено,
        чтобы не повторять проверку на уменьшение cwnd до следующей активности */
    SOCK_FLAGS_IDLE                  = 0x00001000,
    /* признак, что есть действительное значение ts_recent для проверки PAWS */
    SOCK_FLAGS_TS_RECENT_VALID       = 0x00002000,
    /* признак, что во время FastRecovery уже приняли частичный ack */
    SOCK_FLAGS_PARTIAL_ACK           = 0x00004000,
    /* признак, что для данного соединения другой стороной разрешена опция Windows Scale */
    SOCK_FLAGS_WS_EN                 = 0x00008000,
    /* признак, что для данного соединения другой стороной разрешена опция Timestamp */
    SOCK_FLAGS_TIMESTAMP_EN          = 0x00010000,
    /* признак, что для данного соединения другой стороной разрешена опция SACK */
    SOCK_FLAGS_SACK_EN               = 0x00020000,
    /* признак, что явно задан адрес удаленного хоста */
    SOCK_FLAGS_FIXED_REM_IP          = 0x00040000,
    /* признак, что явно задан порт удаленного хоста */
    SOCK_FLAGS_FIXED_REM_PORT        = 0x00080000,
    /* признак, что для соединения задан таймаут на завершение закрытия */
    SOCK_FLAGS_CLOSE_TOUT            = 0x00100000
} t_lip_tcp_sock_flags;

/* список флагов, которые сохраняются сохраняются между соединениями */
#define LIP_TCP_SOCK_STATE_FLAGS_PERSIST_MASK   (SOCK_FLAGS_FIXED_REM_IP |\
                                            SOCK_FLAGS_FIXED_REM_PORT | \
                                            SOCK_FLAGS_RX_DROP_MODE)


/***************** флаги, показывающие, какие опции сокета используются **********/
//tcp будет проверять периодически живой ли сокет, если тот долго простаивает
#define LIP_TCP_SOCK_OPT_KEEPALIVE         0x01
//запрет на использование алгоритма Нагла
#define LIP_TCP_SOCK_OPT_NAGLE_DISABLE     0x02



/* коэффициенты, используемые для расчета rto */
#define LIP_TCP_RTO_ALPHA                  (0.125F)
#define LIP_TCP_RTO_BETA                   (0.25F)

/* начальное значение ssthresh (должно быть большое в соответствии с rfc5681) */
#define LIP_TCP_INIT_SSTHRESH           65535



#define TCP_HDR_GET_HDR_SIZE(hdr)            (((hdr)->offset &0xF0) >> 2)
#define TCP_HDR_SET_HDR_SIZE(hdr, hdr_size)  ((hdr)->offset = (hdr_size) << 2)



/* максимальное количество подтверждаемых сегментов SACK-опцией */
#define LIP_TCP_SACK_SEGS_MAX 4



#define TCP_SEG_SEQ_LEN(len, flags) ((len) + ((flags) & LIP_TCP_FLAGS_SYN  ? 1 : 0) + \
                                    ((flags) & LIP_TCP_FLAGS_FIN  ? 1 : 0))


#define SOCK_RECV_FIFO_RDY_SIZE(psock) (((psock)->state_flags & SOCK_FLAGS_RX_DROP_MODE) ? 0 : \
               (psock)->rcv_fifo.size - ((psock)->tcb.rcv_wnd + (psock)->rcv_wnd_reduction))


typedef struct {
    t_lclock_ticks trans_time;
    uint32_t seq_num;
    uint16_t len;
    uint16_t flags;
} t_tcp_lip_retrans_seg;

typedef struct {
    uint32_t first;
    uint32_t next;
} t_lip_tcp_recv_parts;


#include "lcspec_pack_start.h"
/* заголовок tcp пакета */
struct st_lip_tcp_hdr {
    uint16_t src_port;
    uint16_t dest_port;
    uint32_t seq_number;
    uint32_t ack_number;
    uint8_t offset;
    uint8_t flags;
    uint16_t window;
    uint16_t checksum;
    uint16_t urgent_ptr;
    uint8_t opt[0];
} LATTRIBUTE_PACKED;
typedef struct st_lip_tcp_hdr t_lip_tcp_hdr;

#define LIP_TCP_STD_HDR_SIZE  sizeof(struct st_lip_tcp_hdr)

/* псевдозаголовок, используемый для вычисления суммы в TCP */
struct st_lip_tcp_chk_hdr {
    uint8_t src_addr[LIP_IPV4_ADDR_SIZE];
    uint8_t dst_addr[LIP_IPV4_ADDR_SIZE];
    uint8_t zero;
    uint8_t protocol;
    uint16_t length;
} LATTRIBUTE_PACKED;
typedef struct st_lip_tcp_chk_hdr t_lip_tcp_chk_hdr;

#define LIP_TCP_CHK_HDR_SIZE sizeof(struct st_lip_tcp_chk_hdr)

#include "lcspec_pack_restore.h"

//Transmission Control Block - информация о tcp соединении - из rfc793
typedef struct {
    uint32_t snd_una; //send unacknowledged seq
    uint32_t snd_nxt; //send next seq
    uint32_t rcv_wnd; //receive window
    uint32_t rcv_nxt; //receive next

    uint32_t snd_wnd; //send window
    //uint32_t snd_up;  //send urgent pointer
    uint32_t snd_wl1; //segment sequence number used for last window update
    //uint32_t snd_wl2; //segment acknowledgment number used for last window update
    uint32_t iss;     //initial send sequence number

    //uint32_t rcv_up;  //receive urgent pointer
    //uint32_t irs;     //initial receive sequence number
} t_lip_tcb;

typedef struct {
    uint8_t* buf; /* указатель массив с очередью */
    uint32_t size; /* размер очереди в байтах */
    uint32_t cons_ptr; /* указатель на слово, следующее за последним прочитанным из очереди */
    uint32_t prod_ptr; /* указатель на слово, следующее за последним записанным в очередь */
} t_lip_tcp_fifo;




struct st_lip_tcp_sock {
    t_lip_tcb tcb;  //из rfc - информация по seq_num
    t_lip_ip_info rem_ip_info; //используемая информация о IP-уровня при посылке пактов
    uint8_t rem_ip_addr[LIP_IP_ADDR_SIZE_MAX]; //адрес удаленного хоста
    uint16_t rem_tcp_port; //удаленный порт (со стороны хоста, с которым установленно соединение)
    uint16_t tcp_port; //используемый порт
    uint8_t  rem_eth_port; //номер порта ethernet, с которого приходит пакеты
    t_lclock_ticks last_activ_tmr;
    t_lclock_ticks wait_time;
    t_lclock_ticks srtt;   //сглаженное время отклика (RTT)
    t_lclock_ticks rttvar; //разброс времени отклика (RTT)
    t_lclock_ticks rto; /* текущее значение таймаута на повторную передачу */
    t_lclock_ticks retr_time; /* время последней повторной передачи */
    t_lclock_ticks close_time; /* время вызова lsock_close() */
    t_lclock_ticks close_tout; /* таймаут на ожидание заверешния */


    uint8_t retr_cnt; /* количество сегментов в очереди на повторную передачу */
    uint8_t retr_seg_cnt; /* количество сегментов в очереди на повторную передачу */
    uint8_t retr_first; /* индекс первого сегмента (наиболее рано отправленного из неподтвержденных)
                           в массиве retr_seg */
    /* кольцевая очередь сегментов для повторной передачи */
    t_tcp_lip_retrans_seg retr_seg[LIP_TCP_RETRANS_MAX_SEG];

#ifdef LIP_TCP_USE_SACK
    /* номера сегментов из rcv_noncont_parts в том порядке, в котором
     * они должны быть вставлены в посылаемую опцию SACK
     * (по времени изменения, начиная с только что изменившегося) */
    uint8_t rcv_sack_idx[LIP_TCP_RCV_NONCONT_BLOCKS_MAX];
#endif
    uint8_t rcv_noncont_cnt; /* количество сегментов, принятых не по порядку */
    /* информация о принятых не по порядку сегментах */
    t_lip_tcp_recv_parts rcv_noncont_parts[LIP_TCP_RCV_NONCONT_BLOCKS_MAX];

    uint8_t state; /* состояние сокета (rfc793 p23) */
    uint8_t zwnd_prob; /* сколько раз подряд проверяли 0-е окно */
    uint8_t rx_mode;
    uint8_t tx_mode;
#ifdef LIP_TCP_USE_KEEPALIVE
    uint8_t keepalive_cnt; /* кол-во переданных без подтверждения keepalive пакетов */
#endif
    uint8_t dup_ack_cnt; /* количество принятых повторных подтверждений (для FastRetransmit) */
#ifdef LIP_TCP_USE_WINSCALE
    /* значения winscale (rfc1323 p10) */
    uint8_t snd_ws;
    uint8_t rcv_ws;
#endif
    uint16_t rcv_wnd_reduction; /* свободный размер в буфере на прием, но не выделенный
                                   под окно в связи с использованием SWS (rfc1122 4.2.3.3 p97) */
    uint32_t eff_snd_mss; /* максимальный размер сегмента на передачу (rfc1122 4.2.2.6 p85 */
    uint32_t state_flags; /* спец флаги состояния LIP_TCP_SOCK_STATE_FLAGS_XXX */
    uint32_t max_snd_win; /* максимально объявленное другой стороной окно - используется для SWS */
    uint32_t prev_ack;    /* предыдущий подтвержденный номер последовательности */
    /* номер сегмента, который был послан, когда вошли в FastRecovery (rfc6582) */
    uint32_t recover_seq;

#ifdef LIP_TCP_USE_SLOW_START
    uint32_t cwnd; /* congestion window */
    uint32_t ssthresh; /* slow start threshold */
#endif

#ifdef LIP_TCP_USE_TIMESTAMP
    uint32_t ts_recent;
    uint32_t last_ack_sent;
#endif

    uint32_t opt; //опции LIP_TCP_OPT_XXX
    int snd_rdy; /* кол-во данных, которые готовы к передачи */
    union {
#if LIP_TCP_FIFO_MODE_ENABLE
        t_lip_tcp_fifo rcv_fifo;
#endif
#if LIP_TCP_DD_MODE_ENABLE
        struct {
            t_lip_tcp_dd rx_dds[LIP_TCP_SOCK_RX_DD_CNT];
            uint16_t rx_cur_dd;
            uint16_t rx_busy_dd;
        };
#endif
    };
    union {
#if LIP_TCP_FIFO_MODE_ENABLE
        t_lip_tcp_fifo snd_fifo;
#endif
#if LIP_TCP_DD_MODE_ENABLE
        struct {
            t_lip_tcp_dd tx_dds[LIP_TCP_SOCK_TX_DD_CNT];
            uint16_t tx_cur_dd;
            uint16_t tx_busy_dd;
        };
#endif
    };

#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
    t_lip_data_copy_func rx_data_copy;
    void *rx_data_copy_ctx;
    t_lip_data_copy_func tx_data_copy;
    void *tx_data_copy_ctx;
#endif

};


typedef struct st_lip_tcp_sock t_lip_tcp_sock;

//режимы работы сокета
#define LIP_TCP_SOCK_MODE_INVALID     0U
#define LIP_TCP_SOCK_MODE_FIFO        1U //с использованием очереди
#define LIP_TCP_SOCK_MODE_DD          2U //с использованием дескрипторов



static t_lip_errs f_sock_send(t_lip_tcp_sock *psock, uint32_t flags, uint16_t size);
static uint32_t f_sock_get_iw(t_lip_tcp_sock *psock);

//макросы для перехода к номеру следующего дескриптора на прием и передачу соответственно
#define TX_DD_INC(dd) (((dd) == LIP_TCP_SOCK_TX_DD_CNT-1) ? 0 : (dd)+1)
#define RX_DD_INC(dd) (((dd) == LIP_TCP_SOCK_RX_DD_CNT-1) ? 0 : (dd)+1)

#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
    #define LIP_SOCK_DEFAULT_DATA_COPY_FUNC  lip_data_copy_memcpy

    static LINLINE void f_sock_tx_data_cpy(t_lip_tcp_sock *sock, void *dest, const void *src, size_t size) {
        sock->tx_data_copy(dest, src, size, sock->tx_data_copy_ctx);
    }
    static LINLINE void f_sock_rx_data_cpy(t_lip_tcp_sock *sock, void *dest, const void *src, size_t size) {
        sock->rx_data_copy(dest, src, size, sock->rx_data_copy_ctx);
    }
#else
    static LINLINE void f_sock_tx_data_cpy(t_lip_tcp_sock *sock, void *dest, const void *src, size_t size) {
        memcpy(dest, src, size);
    }
    static LINLINE void f_sock_rx_data_cpy(t_lip_tcp_sock *sock, void *dest, const void *src, size_t size) {
        memcpy(dest, src, size);
    }
#endif



static t_lip_tcp_sock f_tcp_sock_list[LIP_TCP_SOCKET_CNT];
static t_lclock_ticks f_retr_check_time;
static t_lclock_ticks f_delayed_ack_tout;


static uint32_t f_cur_iss;


#ifdef LIP_TCP_USE_ACTIVE_CONNECT
    static uint16_t f_last_tmp_port;

    #define LIP_TCP_TMP_PORT_INC(port) ((port) == LIP_TCP_TMP_PORT_LAST ? LIP_TCP_TMP_PORT_FIRST : (port)+1)
#endif

#if LIP_TCP_DD_MODE_ENABLE
    static void f_sock_abort_all_dd(int sock_id, int err, int abort_snd);
#endif

LINLINE static void f_sock_set_closed(int sock_id) {
    t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
    psock->state = LIP_TCP_SOCK_STATE_CLOSED;
    psock->state_flags &= ~SOCK_FLAGS_RX_DROP_MODE;
#if LIP_TCP_DD_MODE_ENABLE
    f_sock_abort_all_dd(sock_id, psock->state_flags & SOCK_FLAGS_CON_ABORTED ?
                        LIP_TCP_DD_STATUS_CON_ABORTED : LIP_TCP_DD_STATUS_CON_CLOSED, 1);
#endif
    /* если сокет завершил соединение и приложению он больше
     * не нужен (вызван destroy) - то освобождаем его */
    if (psock->state_flags & SOCK_FLAGS_CAN_FREE)
        psock->state = LIP_TCP_SOCK_STATE_NOT_IN_USE;
}

#if LIP_TCP_FIFO_MODE_ENABLE
/**************************************************************************************
 *   Вспомогательная функция - копирует данные из буфера в fifo
 *   Параметры:
 *     fifo      - указатель на буфер fifo
 *     fifo_pos  - текущая позиция в fifo (куда будет записан первый байт)
 *     fifo_size - размер fifo в байтах
 *     buf       - указатель на буфер с данными, которые будут записаны
 *     size      - размер записываем данных
 *   Возвращаемое значение:
 *     новый указатель позиции в fifo (первый свободный байт за записанными данными
 ***************************************************************************************/
static int f_cpy_to_fifo_tx(t_lip_tcp_sock *sock, uint8_t *fifo, int fifo_pos, int fifo_size, const uint8_t *buf, int size) {
    int size1;
    int res;

    while (fifo_pos > fifo_size)
        fifo_pos -= fifo_size;

    size1 = MIN(size, fifo_size - fifo_pos);
    f_sock_tx_data_cpy(sock, &fifo[fifo_pos], buf, size1);

    if (size1 == size) {
        res = fifo_pos + size1;
        if (res == fifo_size)
            res = 0;
    } else {
        f_sock_tx_data_cpy(sock, fifo, &buf[size1], size-size1);
        res = size-size1;
    }
    return res;
}

static int f_cpy_to_fifo_rx(t_lip_tcp_sock *sock, uint8_t *fifo, int fifo_pos, int fifo_size, const uint8_t *buf, int size) {
    int size1;
    int res;

    while (fifo_pos > fifo_size)
        fifo_pos -= fifo_size;

    size1 = MIN(size, fifo_size - fifo_pos);
    f_sock_rx_data_cpy(sock, &fifo[fifo_pos], buf, size1);

    if (size1 == size) {
        res = fifo_pos + size1;
        if (res == fifo_size)
            res = 0;
    } else {
        f_sock_rx_data_cpy(sock, fifo, &buf[size1], size-size1);
        res = size-size1;
    }
    return res;
}
#endif


/*******************************************************************
 *  Вспомогательная функция. Сбрасывает параметры в сокете,
 *  которые должны сбрасываться при установке нового соединения
 *  (номера последовательность, указатели в буферах и т.д.)
 *******************************************************************/
static void f_sock_reset_tx_rx_cntrs(t_lip_tcp_sock *psock) {
    psock->snd_rdy = 0;
    psock->tcb.rcv_wnd = 0;
    psock->tcb.snd_una = psock->prev_ack = 0;
    psock->tcb.snd_nxt = 0;
    psock->tcb.rcv_nxt = 0;
    /* сохраняем только флаги, которые должны оставаться между соединениями */
    psock->state_flags &= LIP_TCP_SOCK_STATE_FLAGS_PERSIST_MASK;
    psock->dup_ack_cnt = 0;

    psock->zwnd_prob = 0;
    psock->rcv_wnd_reduction = 0;

    psock->retr_cnt = 0;
    psock->rto = LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_RTO_INIT);
    psock->retr_seg_cnt = psock->retr_first = 0;
    psock->rcv_noncont_cnt = 0;
#ifdef LIP_TCP_USE_WINSCALE
    psock->rcv_ws = psock->snd_ws = 0;
#endif
#ifdef LIP_TCP_USE_KEEPALIVE
    psock->keepalive_cnt = 0;
#endif

#if LIP_TCP_FIFO_MODE_ENABLE
    if (psock->rx_mode==LIP_TCP_SOCK_MODE_FIFO) {
        psock->rcv_fifo.prod_ptr = 0;
        psock->rcv_fifo.cons_ptr = 0;
        psock->tcb.rcv_wnd = psock->rcv_fifo.size;
    }
    if (psock->tx_mode==LIP_TCP_SOCK_MODE_FIFO) {
        psock->snd_fifo.prod_ptr = 0;
        psock->snd_fifo.cons_ptr = 0;
    }
#endif


#if LIP_TCP_DD_MODE_ENABLE
    //если есть поддержка дескрипторов - инициализация дескрипторов в неактивное состояние
    psock->tx_cur_dd = psock->tx_busy_dd = 0;
    psock->rx_cur_dd = psock->rx_busy_dd = 0;
#endif

    if (psock->state_flags & SOCK_FLAGS_RX_DROP_MODE) {
        psock->tcb.rcv_wnd = 0xFFFF;
    }
}




void lip_tcp_init(void) {
#ifdef LIP_TCP_USE_ACTIVE_CONNECT
    f_last_tmp_port = LIP_TCP_TMP_PORT_FIRST;
#endif
    f_retr_check_time = f_delayed_ack_tout = lclock_get_ticks();

#if LIP_TCP_CB_GET_ISS
    /* можем установить сами начальный ISS, например взяв за основу RTC */
    f_cur_iss = lip_tcp_cb_get_iss();
#endif

    for (int i = 0; i < LIP_TCP_SOCKET_CNT; ++i) {
#if LIP_TCP_FIFO_MODE_ENABLE
        f_tcp_sock_list[i].rcv_fifo.size = 0;
#endif
        f_tcp_sock_list[i].state = LIP_TCP_SOCK_STATE_NOT_IN_USE;
    }

    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
            "lip tcp: socket size = %d, segs size = %d, ip_info = %d\n", sizeof(t_lip_tcp_sock), sizeof(t_tcp_lip_retrans_seg) * LIP_TCP_RETRANS_MAX_SEG, sizeof(t_lip_ip_info));
}

void lip_tcp_close(unsigned tout) {
    /* закрытие всех соединений по закрытию стека */
    for (int i = 0; i < LIP_TCP_SOCKET_CNT; ++i) {
        if ((f_tcp_sock_list[i].state != LIP_TCP_SOCK_STATE_NOT_IN_USE) &&
                (f_tcp_sock_list[i].state != LIP_TCP_SOCK_STATE_CLOSED)) {
            lsock_close(SOCK_ID_BY_IDX(i), tout);
        }
    }
}

/***************************************************************************//**
*  Создание нового сокета для обмена по TCP. Сокет соответствует
*  сокету из @rfc{793} с аналогичными состояниями.
*  Функция возвращает идентификатор созданного сокета, который используется во
*  всех остальных функциях. Можно создать одновременно не более
*  #LIP_TCP_USER_SOCKET_CNT сокетов. Если нет свободного места для создания сокета,
*  функция возвращает #LIP_TCP_INVALID_SOCKET_ID.
*
*  @return Идентификатор созданного сокета или #LIP_TCP_INVALID_SOCKET_ID при неудаче
*******************************************************************************/
t_lsock_id lsock_create(void) {
    t_lsock_id ret = LIP_TCP_INVALID_SOCKET_ID;
    for (int i = 0; (i < LIP_TCP_SOCKET_CNT) && (ret < 0); ++i) {
        if (f_tcp_sock_list[i].state == LIP_TCP_SOCK_STATE_NOT_IN_USE) {
            ret = SOCK_ID_BY_IDX(i);
            t_lip_tcp_sock *sock = &f_tcp_sock_list[i];
            sock->state_flags = 0;
            sock->state = LIP_TCP_SOCK_STATE_CLOSED;
            sock->opt = 0;
            sock->rem_ip_info.tos = 0;
            sock->rem_ip_info.ttl = 0;
            sock->tx_mode = sock->rx_mode  = LIP_TCP_SOCK_MODE_INVALID;
#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
            sock->rx_data_copy = LIP_SOCK_DEFAULT_DATA_COPY_FUNC;
            sock->rx_data_copy_ctx = NULL;
            sock->tx_data_copy = LIP_SOCK_DEFAULT_DATA_COPY_FUNC;
            sock->tx_data_copy_ctx = NULL;
#endif
            f_sock_reset_tx_rx_cntrs(sock);
        }
    }
    return ret;
}

/***************************************************************************//**
    Функция возвращает текущее состояние сокета (из #t_lip_tcp_sock_state).
    Состояния сокета соответствуют @rfc{783} p.23, за исключением состояния
    #LIP_TCP_SOCK_STATE_NOT_IN_USE, которое соответствует недействительному сокету.

    @param[in] sock_id    Идентификатор сокета
    @return               Возвращает состояние сокета. Если идентификатор
                          не соответствует инициализированному сокету,
                          то возвращает #LIP_TCP_SOCK_STATE_NOT_IN_USE
*******************************************************************************/
t_lip_tcp_sock_state lsock_state(t_lsock_id sock_id) {
    return (SOCK_VALID(sock_id)) ? SOCK_STRUCT(sock_id)->state : LIP_TCP_SOCK_STATE_NOT_IN_USE;
}

/***************************************************************************//**
    Установка опций, задающих специальные настройки, управляющие поведением
    сокета. Данная функция сделана максимально близко к функции setsockopt()
    BSD-сокетов. Опции разделены на уровни.

    Поддерживаются следующие опции: #SO_KEEPALIVE и #TCP_NODELAY.

    @param[in] sock_id    Идентификатор сокета
    @param[in] level      Уровень, на котором определена опция
    @param[in] opt        Код опции. Для каждого уровня определены свои опции
    @param[in] optval     Указатель на буфер, который содержит значение опции
    @param[in] optlen     Размер данных в буфере optval
    @return               Код ошибки
 ******************************************************************************/
t_lip_errs lsock_set_opt(t_lsock_id sock_id, int level, int opt,
                         const void *optval, socklen_t optlen) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if ((level == IPPROTO_TCP) && (opt == TCP_NODELAY)) {
            if (optlen == sizeof(int)) {
                int val = *((int*)optval);
                if (val) {
                    psock->opt |= LIP_TCP_SOCK_OPT_NAGLE_DISABLE;
                } else {
                    psock->opt &= ~LIP_TCP_SOCK_OPT_NAGLE_DISABLE;
                }
            } else {
                res =  LIP_ERR_TCP_SOCK_INVALID_OPT_LEN;
            }
#ifdef LIP_TCP_USE_KEEPALIVE
        } else if ((level == SOL_SOCKET) && (opt == SO_KEEPALIVE)) {
            if (optlen == sizeof(int)) {
                int val = *((int*)optval);
                if (val) {
                    psock->opt |= LIP_TCP_SOCK_OPT_KEEPALIVE;
                } else {
                    psock->opt &= ~LIP_TCP_SOCK_OPT_KEEPALIVE;
                }
            } else {
               res =  LIP_ERR_TCP_SOCK_INVALID_OPT_LEN;
            }
#endif
        } else {
            res = LIP_ERR_TCP_SOCK_UNSUP_OPTION;
        }
    }
    return res;
}

t_lip_errs lsock_get_rx_eth_port(t_lsock_id sock_id, uint8_t *port) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        *port = psock->rem_eth_port;
    }
    return res;
}


/***************************************************************************//**
    Функция позволяет явно задать порт удаленного хоста.
    Данная функция должна быть вызвана при выполнении активного подключения
    перед вызовом lsock_connect().
    Также данная функция, вызванная перед вызовом lsock_listen(), позволяет указать,
    что данный сокет должен принимать только подключения с заданным портом удаленного
    сокета. Вызов данной функции со значением порта #LIP_TCP_INVALID_PORT
    снимает ранее заданное ограничение и разрешает принимать подключения
    от любого порта.

    @param[in] sock_id     Идентификатор сокета
    @param[in] port        Номер TCP-порта
    @return                Код ошибки
 ******************************************************************************/
t_lip_errs lsock_set_remote_port(t_lsock_id sock_id, uint16_t port) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if ((psock->state == LIP_TCP_SOCK_STATE_CLOSED) || (psock->state == LIP_TCP_SOCK_STATE_LISTEN)) {
            psock->rem_tcp_port = port;
            if (port != LIP_TCP_INVALID_PORT) {
                psock->state_flags |= SOCK_FLAGS_FIXED_REM_PORT;
            } else {
                psock->state_flags &= ~SOCK_FLAGS_FIXED_REM_PORT;
            }
        }
    }
    return res;
}

/***************************************************************************//**
    Функция позволяет явно задать IP-адрес удаленного хоста.
    Данная функция должна быть вызвана при выполнении активного подключения
    перед вызовом lsock_connect().
    Также данная функция, вызванная перед вызовом lsock_listen(), позволяет указать,
    что данный сокет должен принимать только подключения от удаленного хоста 
    с заданным IP-адресом. Вызов данной функции с нулевым указателем 
    на структуру адреса снимает ранее заданное ограничение и разрешает 
    принимать подключения от хоста с любым адресом.

    @param[in] sock_id       Идентификатор сокета
    @param[in] addr          Указатель на структуру с адресом хоста
    @return                  Код ошибки
 ******************************************************************************/
t_lip_errs lsock_set_remote_ip(t_lsock_id sock_id, const t_lip_ip_addr *addr) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if ((psock->state == LIP_TCP_SOCK_STATE_CLOSED) || (psock->state == LIP_TCP_SOCK_STATE_LISTEN)) {
            if (addr==NULL) {
                psock->state_flags &= ~SOCK_FLAGS_FIXED_REM_IP;
            } else {
                res = lip_ip_addr_check(addr, LIP_IP_ADDR_TYPE_UNICAST);
                if (res == LIP_ERR_SUCCESS) {
                    psock->rem_ip_info.addr_len = addr->addr_len;
                    psock->rem_ip_info.type = addr->ip_type;
                    psock->rem_ip_info.da = psock->rem_ip_addr;
                    memcpy(psock->rem_ip_addr, addr->addr, addr->addr_len);
                    psock->state_flags |= SOCK_FLAGS_FIXED_REM_IP;
                }
            }
        }
    }
    return res;
}


/***************************************************************************//**
    Функция позволяет получить номер TCP-порта удаленного хоста для данного сокета.
    Если порт не определен (соединение не установлено и порт явно не установлен
    с помощью lsock_set_remote_port()), то возвращается значение порта
    #LIP_TCP_INVALID_PORT.

    @param[in]  sock_id     Идентификатор сокета
    @param[out] port        Номер порта удаленного хоста
    @return                 Код ошибки
 *****************************************************************************/
t_lip_errs lsock_get_remote_port(t_lsock_id sock_id, uint16_t *port) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        *port = SOCK_STRUCT(sock_id)->rem_tcp_port;
    }
    return res;
}

/***************************************************************************//**
   Функция позволяет получить адрес удаленного хоста для данного сокета.
   Если адрес не определен (соединение не установлено и адрес явно не установлен
   с помощью lsock_set_remote_ip()), то возвращается код ошибки
   #LIP_ERR_IP_INVALID_ADDR и возвращается адрес с нулевой длиной.

 * @param[in]  sock_id     Идентификатор сокета
 * @param[out] addr        Информация о адресе удаленного хоста
 * @return                 Код ошибки
 *****************************************************************************/
t_lip_errs lsock_get_remote_ip(t_lsock_id sock_id, t_lip_ip_addr *addr) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        addr->addr_len = psock->rem_ip_info.addr_len ;
        addr->ip_type = psock->rem_ip_info.type;
        if (psock->rem_ip_info.addr_len!=0) {
            memcpy(addr->addr, psock->rem_ip_addr, addr->addr_len);
        } else {
            res = LIP_ERR_IP_INVALID_ADDR;
        }
    }
    return res;
}

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

    По-умолчанию сокет принимает соединения от хоста с любым удаленным IP-адресом
    и любым удаленным портом, однако можно установить, чтобы принимались 
    подключения только от хоста с определенным адресом и/или портом, установив 
    их перед вызовом lsock_listen() с помощью lsock_set_remote_ip() и 
    lsock_set_remote_port() соответственно.

    @param[in] sock_id    Идентификатор сокета
    @param[in] port       Номер TCP-порта, который будет прослушивать данный сокет
    @return               Код ошибки
*******************************************************************************/
t_lip_errs lsock_listen(t_lsock_id sock_id, uint16_t port) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->state == LIP_TCP_SOCK_STATE_CLOSED) {
            /* сбрасываем параметры очередей */
            psock->state_flags &= ~SOCK_FLAGS_ACTIVE_OPEN;
            psock->state = LIP_TCP_SOCK_STATE_LISTEN;
            psock->tcp_port = port;

            /* Если не заданы удаленный порт и/или IP-адрес, то сбрасываем
             * возможный оставшийся адрес от предыдущего соединения */
            if (!(psock->state_flags & SOCK_FLAGS_FIXED_REM_PORT)) {
                psock->rem_tcp_port = 0;
            }
            if (!(psock->state_flags & SOCK_FLAGS_FIXED_REM_IP)) {
                psock->rem_ip_info.addr_len = 0;
            }
            f_sock_reset_tx_rx_cntrs(psock);
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_STATE;
        }
    }
    return res;
}

/***************************************************************************//**
    После вызова данной функции все принимаемые сокетом данные будут отброшены
    без уведомления прикладного уровня. При этом объявляется максимально
    возможное окно на прием.

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

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

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

    @param[in] sock_id    Идентификатор сокета
    @return               Код ошибки
*******************************************************************************/
t_lip_errs lsock_set_recv_drop_mode(t_lsock_id sock_id) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        psock->state_flags |= SOCK_FLAGS_RX_DROP_MODE;
        psock->tcb.rcv_wnd = 0xFFFF;
        if ((psock->state == LIP_TCP_SOCK_STATE_ESTABLISHED) ||
             (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1) ||
             (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT2)) {
            psock->state_flags |= SOCK_FLAGS_DELAYED_ACK;
        }
    }
    return res;
}

#ifdef LIP_TCP_USE_ACTIVE_CONNECT
/***************************************************************************//**
    Для включения данной функции в сборку должен быть определен макрос
    #LIP_TCP_USE_ACTIVE_CONNECT в файле конфигурации.

    При вызове данной функции инициируется активное подключение к удаленному
    хосту.
    Сокет должен быть создан с помощью lsock_create(), но соединение должно быть
    закрыто.
    При этом, до вызова данной функции должны быть установлены
    адрес и порт удаленного хоста с помощью функций lsock_set_remote_ip() и
    lsock_set_remote_port() соответственно (в противном случае функция вернет
    ошибку #LIP_ERR_TCP_SOCK_UNSPEC_REM_HOST).
    Локальный порт выбирается из диапазона временных портов от
    #LIP_TCP_TMP_PORT_FIRST до #LIP_TCP_TMP_PORT_LAST (включительно).

    @param[in] sock_id    Идентификатор сокета
    @return               Код ошибки
*******************************************************************************/
t_lip_errs lsock_connect(t_lsock_id sock_id) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->state == LIP_TCP_SOCK_STATE_CLOSED) {
            /* проверяем, что заданы параметры удаленного хоста, к которому
             * нужно подключиться */
            if (!(psock->state_flags & SOCK_FLAGS_FIXED_REM_IP) ||
                    !(psock->state_flags & SOCK_FLAGS_FIXED_REM_PORT)) {
                res = LIP_ERR_TCP_SOCK_UNSPEC_REM_HOST;
            } else {
                int in_use = 1;

                psock->state_flags |= SOCK_FLAGS_ACTIVE_OPEN;
                psock->rem_ip_info.sa = NULL;
                psock->rem_ip_info.ttl = 0;
                psock->rem_ip_info.tos = 0;
                f_sock_reset_tx_rx_cntrs(psock);
                //ищем свободный временный порт
                while (in_use) {
                    /* пробуем последний свободный временный порт и проверяем -
                     * используется ли он кем-либо.
                     * если нет - берем его, иначе - переходим к след. порту
                     */
                    in_use = 0;
                    for (int i = 0; (i < LIP_TCP_SOCKET_CNT) && !in_use; ++i) {
                        if ((f_tcp_sock_list[i].state != LIP_TCP_SOCK_STATE_CLOSED)
                                && (f_tcp_sock_list[i].state != LIP_TCP_SOCK_STATE_NOT_IN_USE)) {
                            if (f_tcp_sock_list[i].tcp_port == f_last_tmp_port) {
                                in_use = 1;
                            }
                        }
                    }

                    if (in_use)
                        f_last_tmp_port = LIP_TCP_TMP_PORT_INC(f_last_tmp_port);
                }

                /* нашли порт => устанавливаем его,
                 f_last_tmp_port увеличиваем на 1, чтобы в след. раз искать со следующего */
                psock->tcp_port = f_last_tmp_port;
                f_last_tmp_port = LIP_TCP_TMP_PORT_INC(f_last_tmp_port);
                psock->state = LIP_TCP_SOCK_STATE_SYN_SENT;

                /* шлем SYN */
                f_sock_send(psock, LIP_TCP_FLAGS_SYN, 0);
            }
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_STATE;
        }
    }
    return res;
}

#endif






/***************************************************************************//**
    Функция возвращает размер данных, которые были приняты сокетом, но еще
    не прочитаны пользователем. Данная функция может работать как в режиме
    обмена через буфер сокета, так и режиме обмена через задания.

    В режиме обмена через буфер функция возвращает количество данных, которые 
    можно вычитать с помощью lsock_recv() (или обработать напрямую и пометить 
    как прочтенные с помощью lsock_recv_fifo_update_pos()).

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

    @param[in] sock_id   Идентификатор сокета
    @return              Если >= 0, то размер принятых данных, иначе ---
                         код ошибки
*******************************************************************************/
int lsock_recv_rdy_size(t_lsock_id sock_id) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
#if LIP_TCP_FIFO_MODE_ENABLE
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_FIFO) {
            res = SOCK_RECV_FIFO_RDY_SIZE(psock);
        }
#endif
#if LIP_TCP_DD_MODE_ENABLE
        if ((psock->rx_mode == LIP_TCP_SOCK_MODE_DD) && (psock->rx_busy_dd!=0))
            res = psock->rx_dds[psock->rx_cur_dd].trans_cnt;
#endif
    }
    return res;
}




#if LIP_TCP_FIFO_MODE_ENABLE

/***************************************************************************//**
  Функция устанавливает выделенный пользователем буфер в качестве буфера
    сокета для сохранения принятых данных.
  Сокет должен быть уже создан lsock_create(), но при этом закрыт
    (состояние сокета #LIP_TCP_SOCK_STATE_CLOSED).
  Принятые данные записываются в этот буфер и хранятся в нем до того как будут 
    прочитаны с помощью lsock_recv() (или обработаны напрямую в буфере и помечены 
    на как прочтенные с помощью lsock_recv_fifo_update_pos()).

    @param[in] sock_id   Идентификатор сокета
    @param[in] buf       Указатель на буфер
    @param[in] size      Размер буфера (в байтах)
*****************************************************************************/
t_lip_errs lsock_set_recv_fifo(t_lsock_id sock_id, uint8_t* buf, int size) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->state == LIP_TCP_SOCK_STATE_CLOSED) {
            psock->rx_mode = LIP_TCP_SOCK_MODE_FIFO;
            psock->rcv_fifo.size = size;
            psock->rcv_fifo.buf = buf;
            psock->rcv_fifo.prod_ptr = 0;
            psock->rcv_fifo.cons_ptr = 0;
            psock->tcb.rcv_wnd = size;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_STATE;
        }
    }
    return res;
}



static void f_rcv_update_wnd(t_lsock_id sock_id, size_t size) {
    t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);

    /* при обновлении окна планируем послать подтверждение,
     * чтобы узнала другая сторона. Если окно было нулевое
     * планируем посылку сразу, чтобы не вносить задержку, иначе -
     * планируем отложенный ack */
    if (psock->tcb.rcv_wnd == 0) {
        psock->state_flags |= SOCK_FLAGS_NEED_ACK;
    } else {
        psock->state_flags |= SOCK_FLAGS_DELAYED_ACK;
    }
    psock->tcb.rcv_wnd += size;

}



LINLINE static void f_update_rcv_ptr(t_lsock_id sock_id, size_t size)  {
    t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);

    psock->rcv_fifo.cons_ptr += size;
    if (psock->rcv_fifo.cons_ptr > psock->rcv_fifo.size)
        psock->rcv_fifo.cons_ptr-= psock->rcv_fifo.size;

    size += psock->rcv_wnd_reduction;
    /* если обновляемый размер больше mss или половины буфера, то
     * обновляем окно, иначе сохраняем размер окна постоянным
     * rfc1122 p.98 */
    if ((size >= psock->eff_snd_mss) || (size >= psock->rcv_fifo.size/2)) {
        psock->rcv_wnd_reduction = 0;
        f_rcv_update_wnd(sock_id, size);
    } else {
        psock->rcv_wnd_reduction = size;
    }
}








/***************************************************************************//**
    Функция копирует данные из буфера сокета в указанных буфер.
    Копируется столько данных, сколько готово для чтения (можно узнать
    lsock_recv_rdy_size()), но не больше указанного в параметре функции.
    Данная функция не блокирует выполнение программы.
    Если все данные были прочитаны из буфера и при этом сокет закрыт другой
    стороной, то функция вернет ошибку #LIP_ERR_TCP_SOCK_CLOSED.

    @param[in] sock_id   Идентификатор сокета
    @param[in] buf       Указатель на буфер, в который будут скопированы
                         данные
    @param[in] size      Максимальный размер копируемых данных
    @return              Если >=0, то количество реально скопированных данных, иначе
                         --- код ошибки
 ******************************************************************************/
int lsock_recv(t_lsock_id sock_id, uint8_t *buf, int size) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        int recvd_size = SOCK_RECV_FIFO_RDY_SIZE(psock);
        switch (psock->state) {
            case LIP_TCP_SOCK_STATE_CLOSE_WAIT:
            case LIP_TCP_SOCK_STATE_CLOSING:
            case LIP_TCP_SOCK_STATE_TIME_WAIT:
            case LIP_TCP_SOCK_STATE_LAST_ACK:
            case LIP_TCP_SOCK_STATE_CLOSED:
                /* если данные еще есть - возвращаем их, иначе - сигнал что закрылись */
                if (recvd_size==0)
                    res = LIP_ERR_TCP_SOCK_CLOSED;
                /* fallthrough */
            case LIP_TCP_SOCK_STATE_LISTEN:
            case LIP_TCP_SOCK_STATE_SYN_SENT:
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            case LIP_TCP_SOCK_STATE_FIN_WAIT1:
            case LIP_TCP_SOCK_STATE_FIN_WAIT2:
                if (res==LIP_ERR_SUCCESS) {
                    int size1;
                    /* размер - минимальный между запрашиваемым и реальным кол-вом данных */
                    size = MIN(size, recvd_size);

                    if (size != 0) {
                        /* получаем размер, сколько можем скопировать непрерывно */
                        size1 = MIN((unsigned)size, psock->rcv_fifo.size - psock->rcv_fifo.cons_ptr);
                        f_sock_rx_data_cpy(psock, buf, &psock->rcv_fifo.buf[psock->rcv_fifo.cons_ptr], size1);
                        if (size1 != size) {
                            /* если есть еще данные вначале, копируем из начала */
                            f_sock_rx_data_cpy(psock, &buf[size1], psock->rcv_fifo.buf, size - size1);
                        }
                        f_update_rcv_ptr(sock_id, size);
                    }
                    res = size;
                }
                break;
            default:
                res = LIP_ERR_TCP_SOCK_CLOSED;
                break;
        }
    }
    return res;
}



/***************************************************************************//**
    Функция получает позицию в буфере сокета на прием, с которой начинаются
    принятые, но не прочитанные из очереди данные. Количество действительных
    данных, начиная с этой позиции, можно получить с помощью lsock_recv_rdy_size().
    Может использоваться при обработке принятых данных в буфере сокета без копирования.

    @param[in]  sock_id   Идентификатор сокета
    @param[out] pos       Позиция в буфере
    @return               Код ошибки
 ******************************************************************************/
t_lip_errs lsock_recv_fifo_get_pos(t_lsock_id sock_id, int* pos) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_FIFO) {
            *pos = (int)psock->rcv_fifo.cons_ptr;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_MODE;
        }
    }
    return res;
}

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


    @param[in] sock_id   Идентификатор сокета
    @param[in] size      Размер, на который нужно увеличить позицию
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_recv_fifo_update_pos(t_lsock_id sock_id, int size) {
    int rdy_size = lsock_recv_rdy_size(sock_id);
    t_lip_errs res = rdy_size < 0 ? rdy_size : size > rdy_size ? LIP_ERR_TCP_SOCK_UPDATE_SIZE : LIP_ERR_SUCCESS;
    if (res == LIP_ERR_SUCCESS) {
        f_update_rcv_ptr(sock_id, size);
    }
    return res;
}


/***************************************************************************//**
    Функция получает позицию в буфере сокета на передачу, с которой начинается
    свободное место, в которое должны быть помещены следующие данные для передачи.
    Размер свободного места можно узнать с помощью lsock_send_rdy_size().
    Может использоваться при подготовке данных на передачу непосредственно
    в буфере сокета без копирования.

    @param[in]  sock_id   Идентификатор сокета
    @param[out] pos       Позиция в буфере
    @return               Код ошибки
 ******************************************************************************/
t_lip_errs lsock_send_fifo_get_pos(t_lsock_id sock_id, int* pos) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->tx_mode == LIP_TCP_SOCK_MODE_FIFO) {
            *pos = (int)psock->snd_fifo.prod_ptr;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_MODE;
        }
    }
    return res;
}


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

    @param[in] sock_id   Идентификатор сокета
    @param[in] size      Размер, на который нужно увеличить позицию
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_send_fifo_update_pos(t_lsock_id sock_id, int size) {
    int rdy_size = lsock_send_rdy_size(sock_id);
    t_lip_errs res = rdy_size < 0 ? rdy_size : size > rdy_size ? LIP_ERR_TCP_SOCK_UPDATE_SIZE : LIP_ERR_SUCCESS;
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        psock->snd_fifo.prod_ptr += size;
        if (psock->snd_fifo.prod_ptr > psock->snd_fifo.size)
            psock->snd_fifo.prod_ptr-= psock->snd_fifo.size;
        psock->snd_rdy += size;
    }
    return res;
}


/***************************************************************************//**
  Функция устанавливает выделенный пользователем буфер в качестве буфера
    сокета для передачи данных.
  Сокет должен быть уже создан lsock_create(), но при этом закрыт
    (состояние сокета #LIP_TCP_SOCK_STATE_CLOSED).

    @param[in] sock_id   Идентификатор сокета
    @param[in] buf       Указатель на буфер
    @param[in] size      Размер буфера (в байтах)
*****************************************************************************/
t_lip_errs lsock_set_send_fifo(t_lsock_id sock_id, uint8_t* buf, int size) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->state == LIP_TCP_SOCK_STATE_CLOSED) {
            psock->tx_mode = LIP_TCP_SOCK_MODE_FIFO;
            psock->snd_fifo.size = size;
            psock->snd_fifo.buf = buf;
            psock->snd_fifo.prod_ptr = 0;
            psock->snd_fifo.cons_ptr = 0;
            psock->snd_rdy = 0;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_STATE;
        }
    }
    return res;
}

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

    @param[in] sock_id   Идентификатор сокета
    @return Если >= 0, то размер принятых данных, иначе --- код ошибки
*******************************************************************************/
int lsock_send_rdy_size(t_lsock_id sock_id) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        res = (psock->tx_mode == LIP_TCP_SOCK_MODE_FIFO) ? (int)(psock->snd_fifo.size - psock->snd_rdy) :
                                                            LIP_ERR_TCP_SOCK_INVALID_MODE;
    }
    return res;
}


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


    @param[in] sock_id   Идентификатор сокета
    @param[in] buf       Указатель на буфер с данными для передачи.
    @param[in] size      Максимальный размер копируемых данных.
    @return              Если >=0, то количество реально скопированных, иначе
                         --- код ошибки
 ******************************************************************************/
int lsock_send(t_lsock_id sock_id, const uint8_t* buf, int size) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        switch (psock->state) {
            case LIP_TCP_SOCK_STATE_LISTEN:
                /** @note по rfc783 нужно выполнять активное соединение.
                 *  но не очевидно, что имеет смысл делать такой неявный переход */
                res = LIP_ERR_TCP_SOCK_INVALID_STATE;
                break;
            case LIP_TCP_SOCK_STATE_SYN_SENT:
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            case LIP_TCP_SOCK_STATE_CLOSE_WAIT:
                /* действительное состояние - кладем данные в очеред
                   для последующей передачи */

                /* размер - минимальный между запрашиваемым и раельным кол-вом данных */
                size = MIN(size, (int)(psock->snd_fifo.size - psock->snd_rdy));
                /*копируем данные */
                psock->snd_fifo.prod_ptr = f_cpy_to_fifo_tx(psock, psock->snd_fifo.buf, psock->snd_fifo.prod_ptr,
                                                            psock->snd_fifo.size, buf, size);
                /* увеличиваем размер данных, готовых к передаче */
                psock->snd_rdy += size;
                res = size;
                break;
            default:
                /* в остальных случаях - мы уже закрыли сокет и посылать нельзя */
                res = LIP_ERR_TCP_SOCK_CLOSED;
                break;
        }
    }
    return res;
}

#endif


#if LIP_TCP_DD_MODE_ENABLE
/***************************************************************************//**
    Функция добавляет задание на передачу данных для сокета в конец очереди.
    Всего может быть одновременно добавлено до #LIP_TCP_SOCK_TX_DD_CNT незавершенных
    заданий. Так как данные не копируются из буфера, то до завершения задания
    нельзя изменять данные в переданном буфере.
    Задание завершается, когда все size байт посланы и подтверждены другой
    стороной.
    По завершению задания вызывается пользовательская функция обратного вызова,
    указатель на которую передается в качестве параметра cb.

    @param[in] sock_id      Идентификатор сокета
    @param[in] buf          Указатель на буфер с данными на передачу
    @param[in] size         Размер данных в буфере в байтах
    @param[in] cb           Указатель на функцию, которая будет вызвана, когда
                            задание на передачу завершится
    @return                 Код ошибки
 ******************************************************************************/
t_lip_errs lsock_send_start(t_lsock_id sock_id, const uint8_t* buf, int size, t_lip_tcp_dd_cb cb) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        uint16_t dd;
        switch (psock->state) {
            case LIP_TCP_SOCK_STATE_LISTEN:
                /* не делаем переход в активное состояние...
                   ибо весьма сомнительный вариант, но мб потом добавить */
                res = LIP_ERR_TCP_SOCK_INVALID_STATE;
                break;
            case LIP_TCP_SOCK_STATE_SYN_SENT:
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            case LIP_TCP_SOCK_STATE_CLOSE_WAIT:
                /* действительное состояние - добавляем запрос на передачу в очередь
                   если не был установлен режим передачи через дескрипторы - выставляем */
                if (psock->tx_mode == LIP_TCP_SOCK_MODE_INVALID)
                    psock->tx_mode = LIP_TCP_SOCK_MODE_DD;

                /* если сокет находится в другом режиме - ошибка */
                if (psock->tx_mode != LIP_TCP_SOCK_MODE_DD) {
                    res = LIP_ERR_TCP_SOCK_INVALID_MODE;
#if LIP_TCP_DD_LEN_MAX < 4
                } else if (size > LIP_TCP_DD_LEN_MAX) {
                    res = LIP_ERR_TCP_SOCK_DD_INVALID_SIZE;
#endif
                } else if (psock->tx_busy_dd < LIP_TCP_SOCK_TX_DD_CNT) {
                    /* если есть свободное место для сохранения задания - добавляем */
                    dd = psock->tx_cur_dd + psock->tx_busy_dd;
                    if (dd >= LIP_TCP_SOCK_TX_DD_CNT)
                        dd -= LIP_TCP_SOCK_TX_DD_CNT;

                    /* заполняем поля дескриптора в соответствии с параметрами */
                    psock->tx_dds[dd].buf = (uint8_t*)buf;
                    psock->tx_dds[dd].length = size;
                    psock->tx_dds[dd].trans_cnt = 0;
                    psock->tx_dds[dd].cb = cb;

                    /* увеличиваем кол-во занятых дескрипторов */
                    psock->tx_busy_dd++;

                    /* увеличиваем размер данных, который готовы передать */
                    psock->snd_rdy += size;
                } else {
                    res = LIP_ERR_TCP_SOCK_NO_FREE_DD;
                }
                break;
            default:
                res = LIP_ERR_TCP_SOCK_CLOSED;
                break;
        }
    }
    return res;
}



/***************************************************************************//**
    Функция добавляет задание на прием данных для сокета в конец очереди.
    Всего может быть одновременно добавлено до #LIP_TCP_SOCK_RX_DD_CNT незавершенных
    заданий. 
    По завершению задания вызывается пользовательская функция обратного вызова,
    указатель на которую передается в качестве параметра cb.
    Чтобы узнать, сколько принято данных по текущему активному заданию до того,
    как оно было завершено, можно воспользоваться функцией lsock_recv_rdy_size()

    @param[in]  sock_id     Идентификатор сокета
    @param[out] buf         Указатель на буфер, в которой будут сохранены принятые данные
    @param[in]  size        Размер буфера на прием
    @param[in]  cb          Функция, которая будет вызвана, когда задание на прием
                            завершится (будет принято size байт)
    @return                 Код ошибки
 ******************************************************************************/
t_lip_errs lsock_recv_start(t_lsock_id sock_id, uint8_t* buf,
                                   int size, t_lip_tcp_dd_cb cb) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        uint16_t dd;
        switch (psock->state) {
            /* в close wait в dd-режиме - раз приняли FIN =>
             * все запросы завершены и все данные до обработали
             * => по CLOSE_WAIT - тоже ошибка, а не отдельная обработка
             */
            case LIP_TCP_SOCK_STATE_LISTEN:
            case LIP_TCP_SOCK_STATE_SYN_SENT:
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            case LIP_TCP_SOCK_STATE_FIN_WAIT1:
            case LIP_TCP_SOCK_STATE_FIN_WAIT2:
                /* если не был установлен режим передачи через дескрипторы - выставляем */
                if (psock->rx_mode == LIP_TCP_SOCK_MODE_INVALID)
                    psock->rx_mode = LIP_TCP_SOCK_MODE_DD;

                /* если сокет находится в другом режиме - ошибка */
                if (psock->rx_mode != LIP_TCP_SOCK_MODE_DD) {
                    res = LIP_ERR_TCP_SOCK_INVALID_MODE;
#if LIP_TCP_DD_LEN_MAX < 4
                } else if (size > LIP_TCP_DD_LEN_MAX) {
                    res = LIP_ERR_TCP_SOCK_DD_INVALID_SIZE;
#endif
                    /* проверяем, есть ли свободные дескрипторы */
                } else if (psock->rx_busy_dd < LIP_TCP_SOCK_RX_DD_CNT) {
                    /* получаем номер следующего свободного дескриптора */
                    dd = psock->rx_cur_dd + psock->rx_busy_dd;
                    if (dd >= LIP_TCP_SOCK_RX_DD_CNT)
                        dd -= LIP_TCP_SOCK_RX_DD_CNT;

                    /* заполняем поля дескриптора в соответствии с параметрами */
                    psock->rx_dds[dd].buf = buf;
                    psock->rx_dds[dd].length = size;
                    psock->rx_dds[dd].trans_cnt = 0;
                    psock->rx_dds[dd].cb = cb;

                    psock->rx_busy_dd++;

                    f_rcv_update_wnd(sock_id, size);
                } else {
                    res = LIP_ERR_TCP_SOCK_NO_FREE_DD;
                }
                break;
            default:
                res = LIP_ERR_TCP_SOCK_CLOSED;
                break;
        }
    }
    return res;
}

/***************************************************************************//**
    Функция позволяет получить количество задач на прем для данного сокета,
    поставленных в очередь с помощью lsock_send_recv(), но еще не завершенных.

    @param[in]  sock_id     Идентификатор сокета
    @return                 Если >=0, то количество незавершенных заданий,
                            иначе --- код ошибки
 ******************************************************************************/
int lsock_recv_dd_in_progress(t_lsock_id sock_id) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_DD) {
            res = psock->rx_busy_dd;
        } else if (psock->rx_mode == LIP_TCP_SOCK_MODE_INVALID) {
            res = 0;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_MODE;
        }
    }
    return res;
}

/***************************************************************************//**
    Функция позволяет получить количество задач на передачу для данного сокета,
    поставленных в очередь с помощью lsock_send_start(), но еще не завершенных.

    @param[in]  sock_id     Идентификатор сокета
    @return                 Если >=0, то количество незавершенных заданий,
                            иначе --- код ошибки
 ******************************************************************************/
int lsock_send_dd_in_progress(t_lsock_id sock_id) {
    int res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->tx_mode == LIP_TCP_SOCK_MODE_DD) {
            res = psock->tx_busy_dd;
        } else if (psock->tx_mode == LIP_TCP_SOCK_MODE_INVALID) {
            res = 0;
        } else {
            res = LIP_ERR_TCP_SOCK_INVALID_MODE;
        }
    }
    return res;
}


/***************************************************************************//**
 Функция возвращает состояние задания на прием, которое было начато, но еще не
 завершено. Может использоваться, чтобы узнать, сколько принято данных до завершения
 задания.
 Если все заначатые задания завершены, то функция возвращает ошибку
 #LIP_ERR_TCP_SOCK_NO_DD_IN_PROGRESS.

 @param[in]  sock_id    Идентификатор сокета
 @param[out] dd         Если функция выполнена успешно, то в эту переменную будет
                        сохранена копия текущего задания на прием.
 @return                Код ошибки
 ******************************************************************************/
t_lip_errs lsock_recv_cur_dd(t_lsock_id sock_id, t_lip_tcp_dd *dd) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->rx_mode != LIP_TCP_SOCK_MODE_DD) {
            res = LIP_ERR_TCP_SOCK_INVALID_MODE;
        } else if (psock->rx_busy_dd == 0) {
            res = LIP_ERR_TCP_SOCK_NO_DD_IN_PROGRESS;
        } else {
            *dd = psock->rx_dds[psock->rx_cur_dd];
        }
    }
    return res;
}

#endif


#if LIP_TCP_ENABLE_CUSTOM_DATA_COPY_FUNC
t_lip_errs lsock_set_recv_data_copy_func(t_lsock_id sock_id, t_lip_data_copy_func func, void *func_ctx) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        psock->rx_data_copy = func == NULL ? LIP_SOCK_DEFAULT_DATA_COPY_FUNC : func;
        psock->rx_data_copy_ctx = func_ctx;
    }
    return res;
}
t_lip_errs lsock_set_send_data_copy_func(t_lsock_id sock_id, t_lip_data_copy_func func, void *func_ctx) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        psock->tx_data_copy = func == NULL ? LIP_SOCK_DEFAULT_DATA_COPY_FUNC : func;
        psock->tx_data_copy_ctx = func_ctx;
    }
    return res;
}
#endif


/***************************************************************************
 * Вспомогательная функция - возвращает время таймаута (в клоках таймера!)
 * для повторной передачи
 * на основе инфы из сокета и кол-ва повторных передач, уже завершенных
 ****************************************************************************/
LINLINE static void f_sock_calc_rto(t_lip_tcp_sock *psock) {
    t_lclock_ticks tout;

    if (psock->state_flags & SOCK_FLAGS_RTT_SET) {
        //rto = srtt + max(g, K*RTTVAR), K=4
        tout = psock->srtt + MAX(1, (psock->rttvar << 2));
        tout = MAX(tout, LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_RTO_MIN));
        tout = MIN(tout, LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_RTO_MAX));
    } else {
        /* если был потерян SYN, то после подтверждения возвращаемся
         * к таймауту в 3с, а не 1с (rfc6298 p5 5.7) */
        tout = psock->state_flags & SOCK_FLAGS_SYN_RETR ?
                    LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_RTO_SYN_BACKOFF) :
                    LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_RTO_INIT);
    }
    psock->rto = tout;
}

/* рассчет srtt и rttvar в соответствии с пунктами 2.2 и 2.3 rfc6298 p3 */
static void f_sock_rtt_upd(t_lip_tcp_sock* psock, uint32_t rtt) {
    int rtt_updated = 0;
    if (psock->state_flags & SOCK_FLAGS_RTT_SET) {
        /** @note возможно следует использовать не каждый сегмент, так как
         *  это может приводить к неадекватным значениям BETA и ALPHA */
       t_lclock_ticks abs_d = psock->srtt > rtt ?
               psock->srtt - rtt : rtt - psock->srtt;
       psock->rttvar = (t_lclock_ticks)((1-LIP_TCP_RTO_BETA)*psock->rttvar +
               LIP_TCP_RTO_BETA* abs_d);
       psock->srtt = (t_lclock_ticks)((1-LIP_TCP_RTO_ALPHA)*psock->srtt +
               LIP_TCP_RTO_ALPHA * rtt);

       rtt_updated = 1;
    } else {
       /* инициализация переменных при первом измерении rtt */
       rtt_updated = 1;
       psock->srtt = rtt;
       psock->rttvar = (rtt >> 1);
       psock->state_flags |= SOCK_FLAGS_RTT_SET;
    }

    if (rtt_updated) {
        f_sock_calc_rto(psock);
#ifdef LIP_TCP_PRINT_RTT_STAT
       lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_LOW, 0,
               "lip tcp: rto update. rtt = %d, srtt = %d, rttvar = %d, rto = %d\n",
                  rtt, psock->srtt, psock->rttvar, psock->rto);
#endif
    }
}


/*************************************************************************
 *  Вспомогательная функция - удаляет подтвержденные сегменты из
 *  очереди ожидающих на повторную передачу сегментов
 *  Кроме того обновляются значения для srtt и rttvar для рассчета
 *  таймаута на повторную передачу
 ************************************************************************/
static void f_sock_drop_retr(t_lip_tcp_sock* psock, uint32_t ack_upd, uint32_t techo, int ts_present) {
    t_lclock_ticks cur_time = lclock_get_ticks();

    if (ack_upd > 0) {
        if (!(psock->retr_seg[psock->retr_first].flags
              & (LIP_TCP_FLAGS_SYN | LIP_TCP_FLAGS_FIN))) {
#ifdef LIP_TCP_USE_TIMESTAMP
            if (psock->state_flags & SOCK_FLAGS_TIMESTAMP_EN) {
                /* если разрешено timestamp - то используем его для расчета RTT
                 * (см. rfc1323 3.3) */
                if (ts_present) {
                    f_sock_rtt_upd(psock, cur_time - techo);
                }
            } else {
#endif
                /* если timestamp не разрешен для соединения, то используем время
                 * отправки сегмента. При этом не используем сегменты с повторами
                 * в соответствии с алгоритмом Карна (Karn) - rfc6298 p4 3. */
                if ((psock->retr_seg[psock->retr_first].len <= ack_upd)  && (psock->retr_cnt==0)) {
                    f_sock_rtt_upd(psock, cur_time - psock->retr_seg[psock->retr_first].trans_time);
                }
#ifdef LIP_TCP_USE_TIMESTAMP
            }
#endif
        }
    }

    while (ack_upd > 0) {
        t_tcp_lip_retrans_seg* seg = &psock->retr_seg[psock->retr_first];
        uint32_t len = TCP_SEG_SEQ_LEN(seg->len, seg->flags);

        if (len <= ack_upd) {
            /* если подтвердили весь сегмент - можно выкинуть */
            SOCK_INC_RETR_CNT(psock->retr_first);
            psock->retr_seg_cnt--;
            /* смотрим, сколько осталось */
            ack_upd-=len;

            /* после подтверждения сегмента сбрасываем кол-во повторов */
            if (psock->retr_cnt!=0) {
                psock->retr_cnt = 0;
                /* возвращаем таймаут на старое значение */
                f_sock_calc_rto(psock);
            }
        } else {
            /* если подтвердили часть сегмента - обновляем параметры
              сегмента на повторную передачу - выкидываем подтвержд. часть */
            if (seg->flags & LIP_TCP_FLAGS_SYN) {
                ack_upd--;
                seg->flags &= ~LIP_TCP_FLAGS_SYN;
            }
            seg->seq_num+=ack_upd;
            seg->len-=ack_upd;
            ack_upd = 0;
        }
    }
}

#if LIP_TCP_DD_MODE_ENABLE
/****************************************************************
 *  Вспомогательная функция - завершает все запросы, находящиеся
 *  в обслуживании с заданным кодом
 ****************************************************************/
static void f_sock_abort_all_dd(int sock_id, int err, int abort_snd) {
    t_lip_tcp_sock* psock = &f_tcp_sock_list[sock_id];
    if (abort_snd && (psock->tx_mode == LIP_TCP_SOCK_MODE_DD)) {
        while (psock->tx_busy_dd) {
            t_lip_tcp_dd *pdd = &psock->tx_dds[psock->tx_cur_dd];
            if (pdd->cb!=NULL)
                pdd->cb(sock_id, pdd, err);
            psock->tx_cur_dd = TX_DD_INC(psock->tx_cur_dd);
            psock->tx_busy_dd--;
        }
    }

    if (psock->rx_mode == LIP_TCP_SOCK_MODE_DD) {
        while (psock->rx_busy_dd) {
            t_lip_tcp_dd *pdd = &psock->rx_dds[psock->rx_cur_dd];
            if (pdd->cb!=NULL)
                pdd->cb(sock_id, pdd, err);
            psock->rx_cur_dd = RX_DD_INC(psock->rx_cur_dd);
            psock->rx_busy_dd--;
       }
    }
}
#endif


#if !LIP_LL_PORT_FEATURE_HW_TX_TCP_CHECKSUM
/* Расчет контрольной суммы по двум смеженным сегментам. Используется
 * для случая, если пакет занимает две части буфера. Учитывает, что первая
 * часть может иметь нечетное кол-во байт */
static uint16_t f_checksum_2seg(uint16_t checksum, const uint8_t* data1, unsigned size1,
                                const uint8_t *data2, unsigned size2) {
    /* отдельно рассматривается случай, когда в первом буфере нечетное кол-во байт,
     * так как одно слово получается разорвано и требует отдельного вычисления
     * контрольной суммы */
    if ((size1 & 1) && (size2 != 0)) {
        uint8_t tmp[2] = {data1[size1-1], data2[0]};
        size1--;
        size2--;
        data2++;
        if (size1 != 0) {
            checksum = lip_chksum(~checksum, data1, size1);
        }
        checksum = lip_chksum(~checksum, tmp, sizeof(tmp));
        if (size2 != 0) {
            checksum = lip_chksum(~checksum, data2, size2);
        }
    } else {
        if (size1 != 0) {
            checksum = lip_chksum(~checksum, data1, size1);
        }
        if (size2 != 0) {
            checksum = lip_chksum(~checksum, data2, size2);
        }
    }
    return checksum;
}
#endif


static int f_send_data(t_lip_tcp_sock *psock, uint8_t *pkt, unsigned hdr_size,
                       const uint8_t *data1, unsigned size1,
                       const uint8_t *data2, unsigned size2) {

    int err = 0;
    /* чтобы можно было считать, что второй буфер всегда последний, то если
     * есть только первый, переназначаем его на второй */
    if (size2==0) {
        size2 = size1;
        data2 = data1;
        size1 = 0;
    }
#if !LIP_LL_PORT_FEATURE_HW_TX_TCP_CHECKSUM
    t_lip_tcp_hdr *hdr = (t_lip_tcp_hdr*)pkt;
    hdr->checksum = f_checksum_2seg(hdr->checksum, data1, size1, data2, size2);
    hdr->checksum = HTON16(hdr->checksum);
#endif
    if ((data1 != &pkt[hdr_size]) && (size1 != 0)) {
        f_sock_tx_data_cpy(psock, &pkt[hdr_size], data1, size1);
    }

#ifdef LIP_TCP_SPLIT_SEND_DATA_SIZE
    if (size2 > LIP_TCP_SPLIT_SEND_DATA_SIZE) {
        err = lip_ip_flush_split(hdr_size+size1, data2, size2);
    } else {
#endif
        memcpy(&pkt[hdr_size+size1], data2, size2);
        err = lip_ip_flush(hdr_size+size1+size2);
#ifdef LIP_TCP_SPLIT_SEND_DATA_SIZE
    }
#endif
    return err;
}





/**************************************************************************
 *  Передача пакета TCP
 *  Внутренняя низкоуровневая функция
 *  Служит как для передачи данных, так и для передачи SYNC/FIN/ACK...
 *
 *  psock - указатель на структуру сокета
 *  flags - флаги - младшие 6 - стандартные флаги TCP, остальные - флаги для функции
 *          из LIP_TCP_SENDFLAGS
 *  data - данные для передачи (если size == 0 - не имеют значения)
 *  size - размер данных в байтах
 ***************************************************************************/
static t_lip_errs f_sock_send(t_lip_tcp_sock *psock, uint32_t flags, uint16_t size) {
    uint16_t hdr_size = LIP_TCP_STD_HDR_SIZE;
    int max_size;
    t_lip_errs err = LIP_ERR_SUCCESS;
    t_lip_tcp_chk_hdr chk_hdr;

    if (flags & LIP_TCP_SENDFLAGS_RETRANS) {
        /* если повторная передача - то данные и их размер берутся из текущего retr_seg */
        size = psock->retr_seg[psock->retr_first].len;
        flags |= psock->retr_seg[psock->retr_first].flags;
#ifdef LIP_TCP_USE_SLOW_START
        /* запоминаем, что была повторная передача SYN, для правильной установки cwnd */
        if (flags & LIP_TCP_FLAGS_SYN) {
            psock->state_flags |= SOCK_FLAGS_SYN_RETR;
            /* если в retr уже есть флаг ACK => SYN мы уже приняли
               и начальный iw рассчитали => корректируем из-за потери SYN */
            if (flags & LIP_TCP_FLAGS_ACK)
                psock->cwnd = f_sock_get_iw(psock);
        }
#endif
    } else {
        /* если передаем новый сегмент - проверяем, что есть место
           для сохранения информации для повторной передачи */
        if (psock->retr_seg_cnt == LIP_TCP_RETRANS_MAX_SEG) {
            /* если нет места для сохр. сегмента - сразу выходим */
            err = LIP_ERR_TCP_SOCK_MAX_RETR_CNT;
        }
    }

    /***********************************************************************//**
     * @todo в соответствии с rfc1122 page 85, нужно проверять максимальный
     *       передаваемый размер не просто по MSS, а с учетом MSS IP-уровня и
     *       опций ip и tcp
     *       min(TCP_MSS+20, IP_MSS_S) - TcpHeader - IPopt
     *******************************************************************/


    if (!err) {
        uint8_t *pkt = lip_ip_prepare(&psock->rem_ip_info, &max_size);
        if (pkt!=NULL) {
            t_lip_tcp_hdr *hdr = (t_lip_tcp_hdr*)pkt;
            uint32_t win;
            uint32_t seq;

            /* сбрасываем флаг, что необходимо послать ack */
            if (flags & LIP_TCP_FLAGS_ACK) {
                psock->state_flags &= ~(SOCK_FLAGS_NEED_ACK
                        | SOCK_FLAGS_DELAYED_ACK | SOCK_FLAGS_RECV_UNACK_SEG);
            }

            /* заполняем поля заголовка */
            hdr->src_port = HTON16(psock->tcp_port);
            hdr->dest_port = HTON16(psock->rem_tcp_port);

            if ((flags & LIP_TCP_FLAGS_SYN) && !(flags & LIP_TCP_SENDFLAGS_RETRANS)) {
                /* при посылке sync - устанавливаем начальный номер последовательности */
                psock->tcb.snd_una = psock->tcb.snd_nxt
                        = psock->recover_seq = psock->prev_ack = psock->tcb.iss = f_cur_iss;
                /* увеличиваем ISS для каждого соединения, как сделано в BSD
                   и описано в Стивенсене (в rfc такого требования не было) */
                f_cur_iss += LIP_TCP_ISS_INC_BY_CON;
            }


            /* определяем seq number для посылаемого пакета */
#ifdef LIP_TCP_USE_KEEPALIVE
            if (flags & LIP_TCP_SENDFLAGS_KEEPALIVE) {
                /* в соответствии с rfc1122 4.2.3.6, шлем в keepalive
                   пакете один garbage байт с seq = snd_nxt - 1 */
                seq = psock->tcb.snd_nxt-1;
                size = 0;
            } else {
#endif
                if (flags & LIP_TCP_SENDFLAGS_RETRANS) {
                    /* для повторной передачи seq_num берем из информации
                     * о повторяемом сегменте */
                    seq = psock->retr_seg[psock->retr_first].seq_num;
                } else {
                    /* если передача не повторная - seq берется из snd_next */
                    seq = psock->tcb.snd_nxt;
                }
#ifdef LIP_TCP_USE_KEEPALIVE
            }
#endif

            hdr->seq_number = HTON32(seq);


            if (flags & LIP_TCP_FLAGS_ACK) {
                hdr->ack_number = HTON32(psock->tcb.rcv_nxt);
            } else {
                hdr->ack_number = 0;
            }


#ifdef LIP_TCP_MAX_WINDOW_SIZE
            win = MIN(psock->tcb.rcv_wnd, LIP_TCP_MAX_WINDOW_SIZE);
#else
            win = psock->tcb.rcv_wnd;
#endif

            hdr->urgent_ptr = 0;

            hdr->flags = flags & LIP_TCP_FLAGSMSK;

            /* При посылке SYNC добавляем специфичные для него опции */
            if (flags & LIP_TCP_FLAGS_SYN) {                
                /* значение R_MSS получаем от IP уровня и посылаем
                 * значение меньше на фиксированный размер заголовка TCP
                 * (rfc1122 p86) */
                uint32_t rmss = lip_ip_get_max_recv_size(&psock->rem_ip_info) - LIP_TCP_STD_HDR_SIZE;
                if (rmss > 0xFFFF)
                    rmss = 0xFFFF;

                pkt[hdr_size++] = LIP_TCP_OPT_CODE_MSS;
                pkt[hdr_size++] = LIP_TCP_OPT_LEN_MSS;
                pkt[hdr_size++] = (rmss & 0xFF00) >> 8;
                pkt[hdr_size++] = rmss & 0xFF;

#ifdef LIP_TCP_USE_WINSCALE
                /* добавляем опцию WinScale всегда в syn без ack (active open)
                 * а в ответный syn, только если ws разрешено (приняли ws в syn)
                 */
                if (!(flags & LIP_TCP_FLAGS_ACK) ||
                        (psock->state_flags & SOCK_FLAGS_WS_EN)) {
                    uint8_t win_scale = LIP_TCP_WINSCALE_MIN;
                    while ((win_scale < LIP_TCP_WINSCALE_MAX)
                            && ((win >> win_scale) > 16384)) {
                        win_scale++;
                    }

                    psock->rcv_ws = win_scale;

                    pkt[hdr_size++] = LIP_TCP_OPT_CODE_NOP;
                    pkt[hdr_size++] = LIP_TCP_OPT_CODE_WS;
                    pkt[hdr_size++] = LIP_TCP_OPT_LEN_WS;
                    pkt[hdr_size++] = win_scale;
                }
#endif
            }

#ifdef LIP_TCP_USE_TIMESTAMP
                /*посылаем timestamp опцию в первом SYN (без ACK) всегда,
                 с остальными сегментами - только если приняли эту опцию
                 от другой стороны, о чем говорит флаг
                 SOCK_FLAGS_TIMESTAMP_EN
                 В RST сегменте timestamp не посылается никогда
                 (рекомендация в rfc1323 page 18) */
            if (((psock->state_flags & SOCK_FLAGS_TIMESTAMP_EN)
                    && !(flags & LIP_TCP_FLAGS_RST))
                 || ((flags & LIP_TCP_FLAGS_SYN)
                         && !(flags & LIP_TCP_FLAGS_ACK))) {

                uint32_t tsval = lclock_get_ticks();
                pkt[hdr_size++] = LIP_TCP_OPT_CODE_NOP;
                pkt[hdr_size++] = LIP_TCP_OPT_CODE_NOP;
                pkt[hdr_size++] = LIP_TCP_OPT_CODE_TIMESTAMP;
                pkt[hdr_size++] = LIP_TCP_OPT_LEN_TIMESTAMP;
                pkt[hdr_size++] = (tsval >> 24) & 0xFF;
                pkt[hdr_size++] = (tsval >> 16) & 0xFF;
                pkt[hdr_size++] = (tsval >>  8) & 0xFF;
                pkt[hdr_size++] = (tsval) & 0xFF;
                pkt[hdr_size++] = (psock->ts_recent >> 24) & 0xFF;
                pkt[hdr_size++] = (psock->ts_recent >> 16) & 0xFF;
                pkt[hdr_size++] = (psock->ts_recent >>  8) & 0xFF;
                pkt[hdr_size++] = (psock->ts_recent) & 0xFF;
            }



            /* обновляем last_ack_sent, который служит для определения ts_recent
               rfc1323 3.4 page 15 пункт 1 */
            if (flags & LIP_TCP_FLAGS_ACK) {
                psock->last_ack_sent = psock->tcb.rcv_nxt;
            }
#endif



#ifdef LIP_TCP_USE_SACK
            if (flags & LIP_TCP_FLAGS_SYN) {
                pkt[hdr_size++]  = LIP_TCP_OPT_CODE_SACK_PERM;
                pkt[hdr_size++]  = LIP_TCP_OPT_LEN_SACK_PERM;
                pkt[hdr_size++]  = LIP_TCP_OPT_CODE_NOP;
                pkt[hdr_size++]  = LIP_TCP_OPT_CODE_NOP;
            } else if ((psock->rcv_noncont_cnt != 0) && (psock->state_flags & SOCK_FLAGS_SACK_EN)) {
                /* если есть принятые данные не по порядку, то добавляем их
                 * в пакет */
                unsigned rem_hdr_size = LIP_TCP_HDR_SIZE_MAX - hdr_size;
                uint8_t snd_sack_cnt = psock->rcv_noncont_cnt;
                uint8_t sack_len;
                /* помещаем в пакет макс. возможное кол-во неподтвержденных
                 * сегментов (на сколько хватит оставшегося размера в заголовке) */
                do {
                    sack_len = 8*snd_sack_cnt + 4;
                    if (sack_len > rem_hdr_size)
                        snd_sack_cnt--;
                } while ((sack_len > rem_hdr_size) && (snd_sack_cnt != 0));

                if (snd_sack_cnt != 0) {
                    pkt[hdr_size++] = LIP_TCP_OPT_CODE_NOP;
                    pkt[hdr_size++] = LIP_TCP_OPT_CODE_NOP;
                    pkt[hdr_size++] = LIP_TCP_OPT_CODE_SACK;
                    pkt[hdr_size++] = sack_len - 2;

                    for (uint8_t i = 0; i < snd_sack_cnt; ++i) {
                        const t_lip_tcp_recv_parts *part = &psock->rcv_noncont_parts[psock->rcv_sack_idx[i]];
                        pkt[hdr_size++] = (part->first >> 24) & 0xFF;
                        pkt[hdr_size++] = (part->first >> 16) & 0xFF;
                        pkt[hdr_size++] = (part->first >>  8) & 0xFF;
                        pkt[hdr_size++] = (part->first) & 0xFF;
                        pkt[hdr_size++] = (part->next >> 24) & 0xFF;
                        pkt[hdr_size++] = (part->next >> 16) & 0xFF;
                        pkt[hdr_size++] = (part->next >>  8) & 0xFF;
                        pkt[hdr_size++] = (part->next) & 0xFF;
                    }
                }
            }
#endif

#ifdef LIP_TCP_USE_WINSCALE
             /* если разрешен WS - то в поле win устанавливаем win>>Rcv.Wind.Scale
                 за исключением сегмента с SYN (rfc1323 p 10) */
            if (!(flags & LIP_TCP_FLAGS_SYN)
                    && (psock->state_flags & SOCK_FLAGS_WS_EN)) {
                win >>= psock->rcv_ws;
            }
#endif
            if (win > 65535)
                win = 65535;
            hdr->window = HTON16(win);


            /* устанавливаем смещение заголовка. Все опции должны быть
             * уже добавлены и hdr_size соответственно увеличен!!! */
            TCP_HDR_SET_HDR_SIZE(hdr, hdr_size);


            /* обновляем размер с учетом используемых опций */
            if (size > ((uint32_t)max_size - hdr_size)) {
                size = max_size - hdr_size;
                /* если передаем меньше, чем запрошено, то убираем
                    флаг PUSH (так как он должен быть в конце запрошенного размера) */
                hdr->flags &= ~LIP_TCP_FLAGS_PSH;
                flags &= ~LIP_TCP_FLAGS_PSH;
            }



            //формируем псевдозаголовок для вычисления контрольной суммы
            chk_hdr.protocol = LIP_PROTOCOL_TCP;
            memcpy(chk_hdr.src_addr, psock->rem_ip_info.sa, psock->rem_ip_info.addr_len);
            memcpy(chk_hdr.dst_addr, psock->rem_ip_info.da, psock->rem_ip_info.addr_len);
            chk_hdr.zero = 0;
            chk_hdr.length = HTON16(hdr_size + size);

            //рассчитываем контрольную сумму по заголовку и псевдозаголовку
            hdr->checksum  = 0;
#if !LIP_LL_PORT_FEATURE_HW_TX_TCP_CHECKSUM
            hdr->checksum = lip_chksum(0, (uint8_t*)hdr, hdr_size);
            hdr->checksum = lip_chksum(~hdr->checksum, (uint8_t*)&chk_hdr, LIP_TCP_CHK_HDR_SIZE);
#endif

            /************************************************************************
             * определяем данные на передачу
             ************************************************************************/
            if (size > 0) {
#if LIP_TCP_FIFO_MODE_ENABLE
                if (psock->tx_mode == LIP_TCP_SOCK_MODE_FIFO) {
                    uint32_t end_size;
                    /* определяем позицию, с которой начинаем передавать данные */
                    uint32_t rdy_pos = psock->snd_fifo.cons_ptr +
                                       (seq - psock->tcb.snd_una);
                    if (rdy_pos >= psock->snd_fifo.size)
                        rdy_pos-= psock->snd_fifo.size;

                    /* размер до конца буфера fifo */
                    end_size = psock->snd_fifo.size - rdy_pos;

                    err = f_send_data(psock, pkt, hdr_size, &psock->snd_fifo.buf[rdy_pos],
                                      MIN(end_size,size), psock->snd_fifo.buf,
                                      size > end_size ? size-end_size : 0);
                }
#endif
#if LIP_TCP_DD_MODE_ENABLE
                if (psock->tx_mode == LIP_TCP_SOCK_MODE_DD) {
                    /* находим место, откуда начинаем посылать данные */
                    uint32_t end_size;
                    uint32_t put_size = 0;
                    uint16_t dd_pos = psock->tx_cur_dd;
                    uint32_t rdy_pos = (uint32_t)psock->tx_dds[dd_pos].trans_cnt + (seq - psock->tcb.snd_una);
                    while (rdy_pos >= psock->tx_dds[dd_pos].length) {
                        rdy_pos-= psock->tx_dds[dd_pos].length;
                        dd_pos = TX_DD_INC(dd_pos);
                    }


                    end_size = MIN(psock->tx_dds[dd_pos].length - rdy_pos, size - put_size);
                    while ((put_size + end_size) < size) {
                        f_sock_tx_data_cpy(psock, &pkt[hdr_size + put_size], &psock->tx_dds[dd_pos].buf[rdy_pos], end_size);
                        put_size += end_size;
                        dd_pos = TX_DD_INC(dd_pos);
                        rdy_pos = 0;
                        end_size = MIN(psock->tx_dds[dd_pos].length, size - put_size);
                    }
                    err = f_send_data(psock, pkt, hdr_size, &pkt[hdr_size], put_size, &psock->tx_dds[dd_pos].buf[rdy_pos], end_size);
                }
#endif
            } else {
#if !LIP_LL_PORT_FEATURE_HW_TX_TCP_CHECKSUM
                hdr->checksum = HTON16(hdr->checksum);
#endif
                err = lip_ip_flush(hdr_size);
            }

            psock->last_activ_tmr = lclock_get_ticks();
            psock->state_flags &= ~SOCK_FLAGS_IDLE;

            /* обновление seq number */
            if (!(flags & (LIP_TCP_SENDFLAGS_RETRANS
                    | LIP_TCP_SENDFLAGS_KEEPALIVE))) {
                uint32_t seq_len = TCP_SEG_SEQ_LEN(size, flags);

                if (seq_len > 0) {
                    /* добавляем буфер в сегменты для повторной передачи */
                    t_tcp_lip_retrans_seg *seg;
                    uint8_t seg_last = psock->retr_first + psock->retr_seg_cnt;
                    if (seg_last >= LIP_TCP_RETRANS_MAX_SEG)
                        seg_last -= LIP_TCP_RETRANS_MAX_SEG;
                    seg = &psock->retr_seg[seg_last];

                    /* если все данные были подтверждены, то RTO был остановлен.
                     * инициализируем его заново */
                    if (psock->retr_seg_cnt == 0) {
                        psock->retr_time = lclock_get_ticks();
                        psock->retr_cnt = 0;
                    }

                    seg->flags = flags;
                    seg->len = size;
                    seg->seq_num = psock->tcb.snd_nxt;
                    seg->trans_time = lclock_get_ticks();

                    psock->retr_seg_cnt++;
                    /* обновляем snd_next */
                    psock->tcb.snd_nxt += seq_len;
                }
            }

            if (flags & LIP_TCP_SENDFLAGS_RETRANS) {
                 psock->retr_seg[psock->retr_first].trans_time = lclock_get_ticks();


                lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                        "lip tcp: port = %d, retrans seg = %#x, cur ack = %#x, seg_cnt = %d, retr_cnt = %d, idx =%d\n",
                            psock->tcp_port,
                            psock->retr_seg[psock->retr_first].seq_num,
                            psock->tcb.snd_una,
                            psock->retr_seg_cnt,
                            psock->retr_cnt,
                            psock->retr_first
                         );

            }
        } else {
            err = max_size;
        }
    }

    return err;

}





/***************************************************************************************
 * Отдельно функция для посылки ответа с RST на пакет, для которого нет сокета
 * (rfc793, p. 36, случай 1)
 *
 * Функция берет значения не из сокета, а из заголовка пришедшего пакета
 * и ip информации пришедшего пакета. Так же исключены все действия по обновлению
 * данных сокета, так как пакет реально не принадлежит сокету
 ***************************************************************************************/
static void f_send_unspec_sock_rst(const t_lip_tcp_hdr *hdr, const t_lip_ip_info *ipinfo, int size) {
    uint16_t hdr_size = LIP_TCP_STD_HDR_SIZE;
    int max_size;
    t_lip_tcp_chk_hdr chk_hdr;

    /* если во входящем пакете есть RST - то отвечать не надо */
    if (!(hdr->flags & LIP_TCP_FLAGS_RST)) {
        t_lip_ip_info snd_ip_info = *ipinfo;
        snd_ip_info.sa = ipinfo->da;
        snd_ip_info.da = ipinfo->sa;
        snd_ip_info.ttl = 0;

        uint8_t* pkt = lip_ip_prepare(&snd_ip_info, &max_size);
        if (pkt != NULL) {
            t_lip_tcp_hdr *snd_hdr = (t_lip_tcp_hdr*)pkt;

            /* заполняем поля заголовка */
            snd_hdr->src_port = hdr->dest_port;
            snd_hdr->dest_port = hdr->src_port;
            snd_hdr->flags = LIP_TCP_FLAGS_RST;
            /* если в принятом пакете есть ack, то seq из пакета, иначе 0 */
            if (hdr->flags & LIP_TCP_FLAGS_ACK) {
                snd_hdr->seq_number =  hdr->ack_number;
                snd_hdr->ack_number = 0;
            } else  {
                snd_hdr->seq_number = 0;
                /* если нет ACK - должны в ответном RST установить ack = seq + seg_len */
                uint32_t seq = NTOH32(hdr->seq_number);
                seq += TCP_SEG_SEQ_LEN(size-TCP_HDR_GET_HDR_SIZE(hdr), hdr->flags);
                snd_hdr->ack_number = HTON32(seq);
                snd_hdr->flags |= LIP_TCP_FLAGS_ACK;
            }


            snd_hdr->window = 0;
            snd_hdr->urgent_ptr = 0;
            TCP_HDR_SET_HDR_SIZE(snd_hdr, hdr_size);



            //формируем псевдозаголовок для вычисления контрольной суммы
            chk_hdr.protocol = LIP_PROTOCOL_TCP;
            memcpy(chk_hdr.src_addr, snd_ip_info.sa, snd_ip_info.addr_len);
            memcpy(chk_hdr.dst_addr, snd_ip_info.da, snd_ip_info.addr_len);
            chk_hdr.zero = 0;
            chk_hdr.length = HTON16(hdr_size);


            //рассчитываем контрольную сумму по заголовку, данным и заголовку ip
            snd_hdr->checksum  = 0;
#if !LIP_LL_PORT_FEATURE_HW_TX_TCP_CHECKSUM
            snd_hdr->checksum = lip_chksum(0, (uint8_t*)snd_hdr, hdr_size);
            snd_hdr->checksum = lip_chksum(~snd_hdr->checksum, (uint8_t*)&chk_hdr, LIP_TCP_CHK_HDR_SIZE);
            snd_hdr->checksum = HTON16(snd_hdr->checksum);
#endif

            lip_ip_flush(hdr_size);

        }
    }
}






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

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

    Так как реально закрытие соединения выполнится только после обмена всеми
    необходимыми сегментами, то данный сокет можно будет использовать для нового
    соединения только после того, как сокет перейдет в состояние 
    #LIP_TCP_SOCK_STATE_CLOSED

    @param[in] sock_id   Идентификатор сокета
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_shutdown(t_lsock_id sock_id) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        switch(psock->state) {
            case LIP_TCP_SOCK_STATE_CLOSED:
            case LIP_TCP_SOCK_STATE_FIN_WAIT1:
            case LIP_TCP_SOCK_STATE_FIN_WAIT2:
                /* ничего не делаем - уже есть запрос на закрытие */
                break;
            case LIP_TCP_SOCK_STATE_LISTEN:
            case LIP_TCP_SOCK_STATE_SYN_SENT:
                /* просто переходим в закрытое состояние */                
                f_sock_set_closed(sock_id);
                break;
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
                /* устанавливаем флаг, что нужно послать FIN после всех данных */
                psock->state = LIP_TCP_SOCK_STATE_FIN_WAIT1;
                psock->state_flags |= SOCK_FLAGS_NEED_FIN;
                break;
            case LIP_TCP_SOCK_STATE_CLOSE_WAIT:
                /* устанавливаем флаг, что нужно послать FIN после всех данных */
                psock->state = LIP_TCP_SOCK_STATE_LAST_ACK;
                psock->state_flags |= SOCK_FLAGS_NEED_FIN;
                break;
        }
    }   
    return res;
}

/***************************************************************************//**
    Вызов данной функции инициирует запрос на закрытие соединения (если оно реально
    было установлено) в виде стандартной для TCP последовательности обмена
    сегментами FIN, аналогично lsock_shutdown(). При этом закрывается сокет не
    только на передачу, но и на прием через lsock_set_recv_drop_mode().

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

    Таким образом, данная функция гарантирует, что соединение будет закрыто
    в течение заданного времени.

    @param[in] sock_id   Идентификатор сокета
    @param[in] tout      Таймаут на закрытие соединения в мс
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_close(t_lsock_id sock_id, uint32_t tout) {
    t_lip_errs res = lsock_shutdown(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        if (psock->state != LIP_TCP_SOCK_STATE_CLOSED) {
            lsock_set_recv_drop_mode(sock_id);
            /* если задан таймаут и не перешли еще в закрытое состояние, то
               устанавливаем таймаут и флаг, что его нужно будет проверять */
            if (tout != LIP_INFINITY_TOUT) {
                psock->close_time = lclock_get_ticks();
                psock->close_tout = LTIMER_MS_TO_CLOCK_TICKS(tout);
                psock->state_flags |= SOCK_FLAGS_CLOSE_TOUT;
            }
        }
    }
    return res;
}


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

   Таймаут задает максимальное время на закрытие сокета, таким образом функция гарантирует,
     что ресурсы будут освобождены в течение заданного времени.

    Вне зависимости от возвращенного кода ошибки после вызова данной функций
     переданный в нее идентификатор сокета использовать больше нельзя.

    @param[in] sock_id   Идентификатор сокета
    @param[in] tout      Таймаут на закрытие соединения в мс
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_destroy(t_lsock_id sock_id, uint32_t tout) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        psock->state_flags |=SOCK_FLAGS_CAN_FREE;
        res = lsock_close(sock_id, tout);
    }
    return res;
}


/**************************************************************************//**
    Если соединение установлено, то при вызове этой функции происходит немедленный
    разрыв соединения с посылкой сегмента RST.
    Все данные, которые были помещены в буфер на передачу, но не были переданы,
    будут выброшены.
    Сокет сразу после вызова этой функции всегда переходит в состояние
    #LIP_TCP_SOCK_STATE_CLOSED и может быть использован для нового соединения.

    @param[in] sock_id   Идентификатор сокета
    @return              Код ошибки
 ******************************************************************************/
t_lip_errs lsock_abort(t_lsock_id sock_id) {
    t_lip_errs res = SOCK_CHECK_ID(sock_id);
    if (res == LIP_ERR_SUCCESS) {
        t_lip_tcp_sock *psock = SOCK_STRUCT(sock_id);
        switch(psock->state) {
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            case LIP_TCP_SOCK_STATE_FIN_WAIT1:
            case LIP_TCP_SOCK_STATE_FIN_WAIT2:
            case LIP_TCP_SOCK_STATE_CLOSE_WAIT:
                /* при установленном соединении шлем сразу RST */
                f_sock_send(psock, LIP_TCP_FLAGS_RST, 0);
                break;                
            case LIP_TCP_SOCK_STATE_LISTEN:
            case LIP_TCP_SOCK_STATE_SYN_SENT:
            case LIP_TCP_SOCK_STATE_CLOSED:
            case LIP_TCP_SOCK_STATE_CLOSING:
            case LIP_TCP_SOCK_STATE_LAST_ACK:
            case LIP_TCP_SOCK_STATE_TIME_WAIT:
                /* ничего не делаем - сразу в закрытое состояние */
                break;
        }        
        /* сокет переходит закрытый режим */
        f_sock_set_closed(sock_id);
    }
    return res;
}



static void f_sock_put_update_pos(int sock_id, uint32_t len) {
    t_lip_tcp_sock *psock = &f_tcp_sock_list[sock_id];
    if (!(psock->state_flags & SOCK_FLAGS_RX_DROP_MODE)) {
#if LIP_TCP_FIFO_MODE_ENABLE
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_FIFO) {
            psock->rcv_fifo.prod_ptr += len;
            if (psock->rcv_fifo.prod_ptr > psock->rcv_fifo.size) {
                psock->rcv_fifo.prod_ptr -= psock->rcv_fifo.size;
            }
        }
#endif
#if LIP_TCP_DD_MODE_ENABLE
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_DD) {           
            while (len > 0) {
                /* получаем текущий дескриптор */
                t_lip_tcp_dd *pdd = &psock->rx_dds[psock->rx_cur_dd];
                /* смотрим - сколько можем макс. принять в буфер данного дескриптора */
                uint32_t cur_len = MIN(len, (uint32_t)(pdd->length - pdd->trans_cnt));
                len -= cur_len;
                pdd->trans_cnt += cur_len;
                if (pdd->length == pdd->trans_cnt) {
                    psock->rx_cur_dd = RX_DD_INC(psock->rx_cur_dd);
                    psock->rx_busy_dd--;
                    if (pdd->cb != NULL) {
                        pdd->cb(sock_id, pdd, LIP_TCP_DD_STATUS_DONE);
                    }

                    if ((psock->rx_busy_dd == 0) && (len > 0)) {
                        lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_ERROR_FATAL, LIP_ERR_INTERNAL_PROCESSING,
                                   "lip tcp: sock recv process error. more data received than rdy dd!\n");
                        lsock_abort(sock_id);
                        break;
                    }

                }
            }
        }
#endif
    }
}



/*********************************************************************
 *  Кладем принятые данные для сокета в нужный буфер
 *********************************************************************/
static void f_sock_put_data(int sock_id, uint32_t offs, const uint8_t *data, uint32_t len) {
   t_lip_tcp_sock *psock = &f_tcp_sock_list[sock_id];
   if (psock->state_flags & SOCK_FLAGS_RX_DROP_MODE) {
       psock->tcb.rcv_wnd = 0xFFFF;
   } else {
#if LIP_TCP_FIFO_MODE_ENABLE
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_FIFO) {
            f_cpy_to_fifo_rx(psock, psock->rcv_fifo.buf, psock->rcv_fifo.prod_ptr + offs,
                             psock->rcv_fifo.size, data, len);
        }
#endif
#if LIP_TCP_DD_MODE_ENABLE
        if (psock->rx_mode == LIP_TCP_SOCK_MODE_DD) {
            uint16_t cur_dd = psock->rx_cur_dd;
            int cur_dd_trans_pos = psock->rx_dds[cur_dd].trans_cnt;

            if (offs > 0) {
                while (offs > (uint32_t)(psock->rx_dds[cur_dd].length - psock->rx_dds[cur_dd].trans_cnt)) {
                    offs -= (psock->rx_dds[cur_dd].length - psock->rx_dds[cur_dd].trans_cnt);
                    cur_dd = RX_DD_INC(cur_dd);
                }
                cur_dd_trans_pos = psock->rx_dds[cur_dd].trans_cnt + offs;
            }

            while (len > 0) {
                /* получаем текущий дескриптор */
                t_lip_tcp_dd *pdd = &psock->rx_dds[cur_dd];

                /* смотрим - сколько можем макс. принять в буфер данного дескриптора */
                uint32_t put_len = MIN(len, (uint32_t)(pdd->length - cur_dd_trans_pos));
                f_sock_rx_data_cpy(psock, &pdd->buf[cur_dd_trans_pos], data, put_len);
                /* обновляем длину и указатель для оставшихся данных */
                len -= put_len;
                data += put_len;

                /* если заполнили весь буфер дескриптора - переходим к следующему */
                if (len > 0) {
                    cur_dd = RX_DD_INC(cur_dd);
                    cur_dd_trans_pos = 0;
                }
            }
        }
#endif
    }
}


/* Удаление сохраненного сегмента идущего не подряд. Выполняется, если
 * дыра до этого была заполнена и сегмент сливается с предыдущем.
 * Обновление индексов для поыслыки в SACK, если опция разрешена */
static void f_recv_noncont_seg_rem(t_lip_tcp_sock *psock, unsigned idx) {
    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
               "lip tcp: remove noncont segment at %d from %d: seq 0x%08X, len %d\n",
               idx, psock->rcv_noncont_cnt, psock->rcv_noncont_parts[idx].first, psock->rcv_noncont_parts[idx].next - psock->rcv_noncont_parts[idx].first);
#ifdef LIP_TCP_USE_SACK
    for (unsigned i = 0; i < (unsigned)(psock->rcv_noncont_cnt - 1); ++i) {
        /* выкидываем указатель на удаляемый блок */
        if (psock->rcv_sack_idx[i] == idx) {
            memmove(&psock->rcv_sack_idx[i], &psock->rcv_sack_idx[i+1],
                    (psock->rcv_noncont_cnt - i - 1) * sizeof(psock->rcv_sack_idx[0]));
        }

        /* индексы блоков за удаляемым уменьшаются соответственно на 1 */
        if (psock->rcv_sack_idx[i] > idx) {
            --psock->rcv_sack_idx[i];
        }
    }
#endif

    if (idx != (unsigned)(psock->rcv_noncont_cnt - 1)) {
        memmove(&psock->rcv_noncont_parts[idx], &psock->rcv_noncont_parts[idx+1],
                (psock->rcv_noncont_cnt - idx - 1)*sizeof(psock->rcv_noncont_parts[0]));
    }
    --psock->rcv_noncont_cnt;
}

/***********************************************************************************
 *   Вспомогательная функция - анализирует данные из принятого пакета TCP
 *   Выполняется проверка seq number, выбор части данных, которая подходит
 *   по номерам и сохранение подходящих данных через f_sock_put_data + посылка ACK
 ***********************************************************************************/
static void f_sock_process_rx_data(int sock_id, uint32_t seq,
                                   const uint8_t *data, uint32_t data_size) {
    //--------------- прием данных в буфер -----------------------
    if (data_size > 0) {
        t_lip_tcp_sock *psock = &f_tcp_sock_list[sock_id];

        /* если начало - повтор, выкидываем повторные данные */
        if ((seq != psock->tcb.rcv_nxt)
                && ((int32_t)(seq - psock->tcb.rcv_nxt) < 0)) {
            uint32_t offs = psock->tcb.rcv_nxt - seq;
            data = &data[offs];
            data_size-= offs;
            seq = psock->tcb.rcv_nxt;
        }

        /* обрезаем данные вне окна, если есть */
        data_size = MIN(data_size, (psock->tcb.rcv_wnd - (seq - psock->tcb.rcv_nxt)));

        f_sock_put_data(sock_id, seq - psock->tcb.rcv_nxt, data, data_size);
        /* прием данных, если идут подряд */
        if (seq == psock->tcb.rcv_nxt) {
            uint32_t up_len = data_size;

            if (psock->rcv_noncont_cnt != 0) {
                /* заполнение дыры (или ее части) требует посылки немедленного подтверждения.
                 * rfc5681 3.2 p8 */
                psock->state_flags |= SOCK_FLAGS_NEED_ACK;

                /* если заполнили дыру до или с пересечением следующего блока не подряд, то
                 * считаем его также принятым, обновляем ack на конец блока(если больше текущего обновления) */
                for (unsigned i = 0; (i < psock->rcv_noncont_cnt) &&
                     ((int32_t)(psock->tcb.rcv_nxt + up_len - psock->rcv_noncont_parts[i].first) >= 0); ++i) {

                    int32_t dlen = (int32_t)(psock->rcv_noncont_parts[i].next - (psock->tcb.rcv_nxt + up_len));
                    if (dlen > 0) {
                        up_len += dlen;
                    }


                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_HIGH, 0,
                               "lip tcp: ACK recv out-of order block at %d from %d: block 0x%08X-0x%08X, seq = 0x%08X, len = %d, dlen = %d\n",
                               i, psock->rcv_noncont_cnt, psock->rcv_noncont_parts[i].first, psock->rcv_noncont_parts[i].next, seq, data_size, dlen);
                    f_recv_noncont_seg_rem(psock, i);
                    --i;
                }
            }

            psock->tcb.rcv_wnd -= up_len;
            psock->tcb.rcv_nxt += up_len;
            f_sock_put_update_pos(sock_id, up_len);

            /* если уже приняли сегмент, на который отложили ack -
             * то ack нужно слать немедленно, иначе - откладываем
             * (rfc5681 пункт 4.2 p11) */
            if (psock->state_flags & SOCK_FLAGS_RECV_UNACK_SEG) {
                psock->state_flags |= SOCK_FLAGS_NEED_ACK;
            } else {
                psock->state_flags |= (SOCK_FLAGS_DELAYED_ACK
                                       | SOCK_FLAGS_RECV_UNACK_SEG);
            }
        } else {
            int fnd_pos = -1;
            int new_noncont_seg = 1;
            /* если данные не подряд - то нужно ack посылать немедленно */
            psock->state_flags |= SOCK_FLAGS_NEED_ACK;


            for (unsigned i = 0; (i < psock->rcv_noncont_cnt) && (fnd_pos < 0); ++i) {
                t_lip_tcp_recv_parts *part = &psock->rcv_noncont_parts[i];
                /* проверяем, что сегмент пересекается или соприкасается
                 * с данной подтвержденной частью */
                if (((uint32_t)(seq - part->first) <= (uint32_t)(part->next - part->first)) ||
                     ((uint32_t)(seq + data_size - part->first) <= (uint32_t)(part->next - part->first))) {

                    int32_t dfirst = (int32_t)part->first - seq;
                    int32_t dlast = (int32_t)(seq + data_size - part->next);

                    new_noncont_seg = 0;
                    fnd_pos = i;
                    /* расширяем ее, если так */
                    if (dfirst > 0)
                        part->first = seq;

                    if (dlast > 0) {
                        part->next += dlast;
                        /* пробуем склеить со следующим, если он прилегает или
                            пересекается с текущим после расширенния новыми данными */
                        while ((i != (unsigned)(psock->rcv_noncont_cnt - 1)) &&
                                ((int32_t)(psock->rcv_noncont_parts[i+1].first - part->next) <= 0)) {
                            /* если окончание следующего сегмента дальше  окончания текущего,
                             * то расширяем текущий, чтобы он поглатил следующий */
                            if ((int32_t)(psock->rcv_noncont_parts[i+1].next - part->next) > 0)
                                part->next = psock->rcv_noncont_parts[i+1].next;

                            /* удаляем следующий сегмент в результате склейки */
                            f_recv_noncont_seg_rem(psock, i+1);
                        }
                    }

                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                               "lip tcp: recv extend out-of order block %d: block start = 0x%08X, end = 0x%08X, seq = 0x%08X, len = %d\n",
                               fnd_pos, part->first, part->next, seq, data_size);
                } else {
                    /* если сегмент находится до проверяемой части, то
                     * значит он не прилегает ни к одному и придется создавать новый */
                    if ((int32_t)(seq + data_size - part->next) < 0) {
                        fnd_pos = i;
                    }
                }
            }

            if (new_noncont_seg) {
                if (psock->rcv_noncont_cnt < LIP_TCP_RCV_NONCONT_BLOCKS_MAX) {
                    if (fnd_pos < 0)
                        fnd_pos = psock->rcv_noncont_cnt;
                    ++psock->rcv_noncont_cnt;

                    /* передвигаем последующие сегменты вправо */
                    for (unsigned i = fnd_pos; i < (unsigned)(psock->rcv_noncont_cnt - 1); ++i) {
                        psock->rcv_noncont_parts[i+1] = psock->rcv_noncont_parts[i];
                    }
                    psock->rcv_noncont_parts[fnd_pos].first = seq;
                    psock->rcv_noncont_parts[fnd_pos].next = seq + data_size;

#ifdef LIP_TCP_USE_SACK
                    /* индекс нового блока должен быть на первом месте */
                    for (int i = psock->rcv_noncont_cnt - 1; i > 0; --i) {
                        psock->rcv_sack_idx[i] = psock->rcv_sack_idx[i - 1];
                        if (psock->rcv_sack_idx[i] >= fnd_pos) {
                            ++psock->rcv_sack_idx[i];
                        }
                    }
                    psock->rcv_sack_idx[0] = fnd_pos;
#endif
                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_HIGH, 0,
                               "lip tcp: recv new out-of order block at %d from %d: rcv_next = 0x%08X, seq = 0x%08X, len = %d\n",
                               fnd_pos, (unsigned)psock->rcv_noncont_cnt, psock->tcb.rcv_nxt, seq, data_size);
                } else {
                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_WARNING_LOW, 0,
                               "lip tcp: recv exceeded out-of order block limit!\n");
                    fnd_pos = -1;
                }
#ifdef LIP_TCP_USE_SACK
            } else {
                if ((psock->rcv_noncont_cnt > 1) && (psock->rcv_sack_idx[0] != fnd_pos)) {
                    /* перемещаем индекс, равный fnd_pos, на первое место, если он не там */
                    int fnd = 0;
                    for (int i = psock->rcv_noncont_cnt - 1; i > 0; --i) {
                        if (psock->rcv_sack_idx[i] == fnd_pos)
                            fnd = 1;
                        if (fnd)
                            psock->rcv_sack_idx[i] = psock->rcv_sack_idx[i-1];
                    }
                    psock->rcv_sack_idx[0] = fnd_pos;
                }
#endif
            }
        }
    }

}


#ifdef LIP_TCP_USE_SLOW_START
/**************************************************************
 *  Получаем начальное окно в соответствии с rfc5681 page 5
 *************************************************************/
static uint32_t f_sock_get_iw(t_lip_tcp_sock* psock) {
    uint32_t iw;
    if (psock->eff_snd_mss <= 1095) {
        iw = 4*psock->eff_snd_mss;
    } else if (psock->eff_snd_mss <= 2190) {
        iw = 3*psock->eff_snd_mss;
    } else {
        iw = 2*psock->eff_snd_mss;
    }

    if (psock->state_flags & SOCK_FLAGS_SYN_RETR)
        iw = psock->eff_snd_mss;
    return iw;
}

static void f_sock_upd_cwnd(t_lip_tcp_sock* psock, uint32_t ack_upd) {
    uint32_t cwnd_up;
    if (psock->cwnd < psock->ssthresh) {
        /* применяем алгоритм slow start (rfc5681 (2) page 6) */
        cwnd_up = MIN(ack_upd, psock->eff_snd_mss);
    } else {
        /* применяем алгоритм congestion avoidance (rfc5681 (3) page 7) */
        /** @todo рассмотреть возможность применения рекомендованного алгоритма
         *  с увеличением cwnd на mss, когда будет подтверждено cwnd байт */
        cwnd_up = psock->eff_snd_mss * psock->eff_snd_mss / psock->cwnd;
        if (cwnd_up == 0)
            cwnd_up = 1;        
    }
    psock->cwnd = UINT32_ADD_SAT(psock->cwnd, cwnd_up);
}

#endif

/**************************************************************************
 *  Обработка пришедшего пакета с SYN флагом в состояниях
 *  LISTEN или SYN_SENT
 *  устанавливаем rcv_nxt по seq-number, который увеличиваем на 1
 *  устанавливаем окно на передачу
 *  ищем опцию с MSS
 **************************************************************************/
static void f_proc_syn_packet(t_lip_tcp_sock *psock,
        const t_lip_tcp_hdr *hdr, uint32_t *seq, const uint8_t *data_start) {

    const uint8_t* opt, *end;
    /* если нет опции mss, то используем 536 в соответствии с rfc1122 4.2.2.6 page 85 */
    uint16_t send_mss = LIP_TCP_DEFAULT_MSS;
    /* сохранить segnum и окно */
    *seq = *seq + 1;
    psock->tcb.rcv_nxt = *seq;
    psock->tcb.snd_wnd = psock->max_snd_win = HTON16(hdr->window);

    opt = hdr->opt;
    end = data_start;
#ifdef LIP_TCP_USE_WINSCALE
    psock->snd_ws = 0; //без ws опции считаем, что win scale = 0
#endif

    while (opt < end) {
        if (*opt == LIP_TCP_OPT_CODE_END ) {
            opt = end;
        } else if (*opt == LIP_TCP_OPT_CODE_NOP) {
            opt++;
        } else {
            if ((*opt == LIP_TCP_OPT_CODE_MSS)
                && (opt[1]==LIP_TCP_OPT_LEN_MSS)) {
                send_mss = ((uint16_t)opt[2] << 8) | opt[3];
#ifdef LIP_TCP_USE_TIMESTAMP
            } else if (*opt == LIP_TCP_OPT_CODE_TIMESTAMP) {
                psock->state_flags |= SOCK_FLAGS_TIMESTAMP_EN;
                psock->ts_recent = (((uint32_t)opt[2]) << 24) |
                        (((uint32_t)opt[3]) << 16) |
                        (((uint32_t)opt[4]) << 8) |
                        (opt[5]);
#endif
#ifdef LIP_TCP_USE_WINSCALE
            } else if (*opt == LIP_TCP_OPT_CODE_WS) {
                psock->state_flags |= SOCK_FLAGS_WS_EN;
                /********************************************************
                 * WS опция не должна быть больше 14 (rfc1323 page 11)
                 * если больше 14 должны использовать 14 и сообщить об ошибке,
                 * но сообщать пока некуда)*/
                psock->snd_ws = opt[2] <= LIP_TCP_WINSCALE_MAX ? opt[2] : LIP_TCP_WINSCALE_MAX;
#endif
#ifdef LIP_TCP_USE_SACK
            } else if (*opt == LIP_TCP_OPT_CODE_SACK_PERM) {
                psock->state_flags |= SOCK_FLAGS_SACK_EN;
#endif
            }
            opt+=opt[1];
        }
    }

    /* rfc1122 4.2.2.6 p85. рассчет Eff.snd.MSS.
       Выбираем минимальное из своего размера и принятого от другой стороной
       (в принятом значении прибавлен размер фикс. части заголовка TCP).
       После чего вычитаем размеры опций TCP и IP
   */
    psock->eff_snd_mss = lip_ip_get_max_send_size(&psock->rem_ip_info) - LIP_TCP_STD_HDR_SIZE;
    if (psock->eff_snd_mss > send_mss)
        psock->eff_snd_mss = send_mss;
    if (psock->state_flags & SOCK_FLAGS_TIMESTAMP_EN)
        psock->eff_snd_mss -= LIP_TCP_OPT_TIMESTAMP_TOTAL_LEN;
    psock->eff_snd_mss -= lip_ip_get_options_size(&psock->rem_ip_info);

#ifdef LIP_TCP_USE_SLOW_START
    psock->cwnd = f_sock_get_iw(psock);
    psock->ssthresh = LIP_TCP_INIT_SSTHRESH;
#endif

}


/*******************************************************************************************
 *   Обработка сокетом входного пакета.......
 **************************************************************************************/
static t_lip_errs lip_tcp_sock_process_packet(int sock_id, const uint8_t* pkt, int size,
                                              const t_lip_ip_rx_packet *ip_packet) {

    t_lip_errs res = LIP_ERR_SUCCESS;
    const t_lip_ip_info *info = &ip_packet->ip_info;
    const t_lip_tcp_hdr *hdr = (const t_lip_tcp_hdr*)pkt;
    t_lip_tcp_sock *psock = &f_tcp_sock_list[sock_id];
    const uint32_t ack = NTOH32(hdr->ack_number);
    const uint32_t pkt_seq = NTOH32(hdr->seq_number);
    const uint8_t hdr_size = TCP_HDR_GET_HDR_SIZE(hdr);
    const int data_size = size - hdr_size;
    const uint8_t *data = &pkt[hdr_size];
    const uint32_t pkt_seq_size = TCP_SEG_SEQ_LEN(data_size, hdr->flags);
    int32_t ack_upd = -1;

    uint32_t seq = pkt_seq;
    int send_retr = 0;
    int hdr_flags = hdr->flags;

#ifdef LIP_TCP_USE_TIMESTAMP
    uint32_t ts_val = 0;
#endif
    uint32_t ts_echo = 0;
    uint8_t  ts_present = 0;

#if LIP_MULTIPORT_ENABLE
    if (ip_packet->eth_packet->ll_frame->info.flags & LIP_FRAME_RXINFO_FLAG_PORT_VALID) {
        psock->rem_eth_port = ip_packet->eth_packet->ll_frame->info.rx_port;
    }
#endif

#if defined LIP_TCP_USE_TIMESTAMP || defined LIP_TCP_USE_SACK
    /* проверяем опции tcp */
    if (hdr_size > LIP_TCP_STD_HDR_SIZE) {
        const uint8_t *opt = hdr->opt;
        const uint8_t *end = data;

        while (opt < end) {
            if (*opt == LIP_TCP_OPT_CODE_END) {
                opt = end;
            } else if (*opt == LIP_TCP_OPT_CODE_NOP) {
                opt++;
            } else {
#ifdef LIP_TCP_USE_TIMESTAMP
                if ((*opt == LIP_TCP_OPT_CODE_TIMESTAMP)
                        && (psock->state_flags & SOCK_FLAGS_TIMESTAMP_EN)) {
                    ts_val = (((uint32_t)opt[2]) << 24) |
                            (((uint32_t)opt[3]) << 16) |
                            (((uint32_t)opt[4]) << 8) |
                            (opt[5]);

                    /* PAWS - если seg.tsval < TS.Recent - сегмент не верен
                       rfc1323 page 19 R1, но только если TS.Recent верен
                       (rfc1323 4.2.3  + page 35)
                       Кроме того, для RST и SYN сегментов PAWS не используется
                      */

                    if (((int32_t)(psock->ts_recent - ts_val) > 0)
                            && !(hdr_flags & (LIP_TCP_FLAGS_RST | LIP_TCP_FLAGS_SYN))
                            && (psock->state_flags & SOCK_FLAGS_TS_RECENT_VALID)) {
                        res = LIP_ERR_TCP_SOCK_INVALID_TS;
                        /* должны послать немедленно ACK (rfc1323 R1 p.19) */
                        psock->state_flags |= SOCK_FLAGS_NEED_ACK;
                    } else {
                        ts_echo = (((uint32_t)opt[6]) << 24) |
                            (((uint32_t)opt[7]) << 16) |
                            (((uint32_t)opt[8]) << 8) |
                            (opt[9]);
                    }
                    ts_present = 1;
                }
#endif
                opt+=opt[1];
            }
        }
    }
#endif

    /* если не listen и syn_sent - проверяем правильность номера последовательности */
    if ((res == LIP_ERR_SUCCESS)
            && (psock->state != LIP_TCP_SOCK_STATE_LISTEN)
            && (psock->state != LIP_TCP_SOCK_STATE_SYN_SENT)
            ) {
        bool last_fin = false;

        /* рассматриваем 4 случая (rfc793 p26) */
        if (pkt_seq_size == 0) {
            if (psock->tcb.rcv_wnd == 0) {
                if (seq != psock->tcb.rcv_nxt) {
                    res = LIP_ERR_TCP_SOCK_INVALID_SEQ_NUM;
                }
            } else {
                if ((seq - psock->tcb.rcv_nxt) >= psock->tcb.rcv_wnd) {
                    res = LIP_ERR_TCP_SOCK_INVALID_SEQ_NUM;
                }
            }
        } else {
            if (psock->tcb.rcv_wnd == 0) {
                /* В случае нулевого окна, если пришел пакет только с флагом FIN,
                 * то из-за того, что FIN соответствует одному байту в номерах
                 * последовательностей, то формально он лежит вне окна и должен быть
                 * отброшен. Однако, т.к. буфер для него не нужен, то возможно его принять
                 * и обработать закрытие сокета, что и пытаемся сделать, чтобы
                 * можно было закрыть сокет с нулевым окном. Однако сама возможность
                 * посылки FIN при нулевом окне может зависеть от реализации,
                 * Windows шлет, а Linux может слаться keep-alive до открытия окна,
                 * и сокет все равно не будет закрыт */
                if ((pkt_seq_size == 1)
                        && (hdr_flags & LIP_TCP_FLAGS_FIN)
                        && (seq == psock->tcb.rcv_nxt)) {
                    last_fin = true;
                } else {
                    res = LIP_ERR_TCP_SOCK_INVALID_SEQ_NUM;
                }
            }  else if (((seq - psock->tcb.rcv_nxt) >= psock->tcb.rcv_wnd) &&
                    ((seq + pkt_seq_size - 1 - psock->tcb.rcv_nxt) >= psock->tcb.rcv_wnd)) {
                /* если начало и конец выходят за окно одновременно - то нет ничего полезного */
                res = LIP_ERR_TCP_SOCK_INVALID_SEQ_NUM;
            }
        }

        if (res == LIP_ERR_SUCCESS) {
            /* если syn не в окне - выкидываем */
            if ((seq != psock->tcb.rcv_nxt) && (hdr_flags & LIP_TCP_FLAGS_SYN)) {
                hdr_flags &= ~LIP_TCP_FLAGS_SYN;
            }

            /* если fin не в окне - выкидываем */
            if (hdr_flags & LIP_TCP_FLAGS_FIN) {
                if (!last_fin && ((seq + pkt_seq_size - 1 - psock->tcb.rcv_nxt) >= psock->tcb.rcv_wnd)) {
                    hdr_flags &= LIP_TCP_FLAGS_FIN;
                }
            }
        }

        /* если номер посл. неверный - пакет не обрабатываем, но шлем
            ответ с верным ack */
        if ((res != LIP_ERR_SUCCESS) && !(hdr_flags & LIP_TCP_FLAGS_RST))
            psock->state_flags |= SOCK_FLAGS_NEED_ACK;
    }


     /** todo здесь можно добавить сравнение QOS и по несовпадению слать RST (page 36) */

    /* общая обработка - флаг ack */
    if ((res == LIP_ERR_SUCCESS) && (hdr_flags & LIP_TCP_FLAGS_ACK)) {
        /* пока находимся в LISTEN - нечего подтверждать =>
           если ack установлен - RST (rfc793 2. page 36) */
        if (psock->state == LIP_TCP_SOCK_STATE_LISTEN) {
            res = LIP_ERR_TCP_SOCK_INVALID_ACK_NUM;
            f_send_unspec_sock_rst(hdr, info, size);
        } else if ((psock->tcb.snd_nxt - psock->tcb.snd_una) >= (psock->tcb.snd_nxt - ack))  {
            /* то проверяем правильность номера ack: snd_una <= ack <= snd_next
             * (rfc793 p25) */
            ack_upd = ack - psock->tcb.snd_una;

            if ((ack_upd > 0) && !(hdr_flags & LIP_TCP_FLAGS_RST)) {
                psock->prev_ack = psock->tcb.snd_una;
                psock->tcb.snd_una = ack;
                /* смотрим - какие сегменты можем выкинуть */
                f_sock_drop_retr(psock, ack_upd, ts_echo, ts_present);
            }
        } else {
            /* не то подтверждение. в несинхронизированном состоянии шлем
             * RST (rfc793 2. page 36) */
            if (SOCK_IS_NON_SYNCRONIZED(psock->state)) {
                f_send_unspec_sock_rst(hdr, info, size);
            } else {
                /* иначе подтверждение с правильными данными (rfc793 3. page 36) */
                psock->state_flags |= SOCK_FLAGS_NEED_ACK;
            }

            /* если в пакете подтверждается то, что еще не слали, то пакет
             * не верен, дальше не обрабатываем.
             * Если же подтверждение старое, то м.б. задержанный пакет */
            if ((int32_t)(psock->tcb.snd_nxt - ack) < 0) {
                res = LIP_ERR_TCP_SOCK_INVALID_ACK_NUM;
            }
        }
    }



    /* обрабатываем условие rst */
    if ((res == LIP_ERR_SUCCESS) && (hdr_flags & LIP_TCP_FLAGS_RST)) {
        /* для состояния SYN SENT признак правильности rst - подтверждение sync
           для других состояний - правильный seq number (проверяется выше)
           rfc793 p37 */
        if ((psock->state != LIP_TCP_SOCK_STATE_SYN_SENT) || (ack_upd > 0)) {
            /* закрываем соединение - переходим в listen или closed */
            if ((psock->state == LIP_TCP_SOCK_STATE_SYN_RECEIVED )
                    && !(psock->state_flags & SOCK_FLAGS_ACTIVE_OPEN)) {
                /* если в SYN_RECEIVED перешли из LISTEN - снова в LISTEN */
                psock->state = LIP_TCP_SOCK_STATE_LISTEN;
            } else if (psock->state != LIP_TCP_SOCK_STATE_LISTEN) {
                /* в LISTEN - игнорируем, в остальных - закрываем соединение */
                lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_HIGH, 0,
                        "lip_tcp: other side reset connection (sock = %d)\n", sock_id);
                /* в соответствии с rfc1122 4.2.2.12 page 87
                    с RST мы должны иметь возможность принять данные */
                f_sock_process_rx_data(sock_id, seq, data, data_size);

                psock->state_flags |= SOCK_FLAGS_CON_ABORTED;
                f_sock_set_closed(sock_id);
            }
        }
    }

    if ((res == LIP_ERR_SUCCESS) && (hdr_flags & LIP_TCP_FLAGS_SYN)) {
        /* если есть SYN flags, а мы его не ждем - ошибка - отмена соединения */
        if ((psock->state != LIP_TCP_SOCK_STATE_LISTEN)
                && (psock->state != LIP_TCP_SOCK_STATE_SYN_SENT)) {
            /* коррекция  (e) из rfc1122 page 94 */
            if ((psock->state == LIP_TCP_SOCK_STATE_SYN_RECEIVED)
                    && !(psock->state_flags & SOCK_FLAGS_ACTIVE_OPEN)) {
                psock->state = LIP_TCP_SOCK_STATE_LISTEN;
                f_sock_reset_tx_rx_cntrs(psock);
            } else {                
                /* шлем сразу RST */
                f_sock_send(psock, LIP_TCP_FLAGS_RST, 0);
                psock->state_flags |= SOCK_FLAGS_CON_ABORTED;
                f_sock_set_closed(sock_id);
            }
        }
    }

    /* обновляем окно */
    if ((res == LIP_ERR_SUCCESS) && (hdr_flags & LIP_TCP_FLAGS_ACK)) {
        if ((psock->state == LIP_TCP_SOCK_STATE_ESTABLISHED) ||
                (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1) ||
                (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT2) ||
                (psock->state == LIP_TCP_SOCK_STATE_CLOSE_WAIT) ||
                (psock->state == LIP_TCP_SOCK_STATE_CLOSING)) {
            /* условия в соответствии с rfc793 page 72
               seg > snd_wl1 или (seq==snd_wl1 и ack>=snd_wl2).
               Вообще это условие не совсем верно, так как может быть пакет
               с повторной передачи с seg < snd_wl1, но ack > snd_una.
               Если есть новое подтверждение, то это в любом случае более
               новый пакет. а если ack==ack_una, то при seg==wl1 не понятно,
               новее или нет пакет, поэтому тоже обрабатываем всегда
               (хотя можно подумать над доп. условиями).
                Поэтому тут ack_una выполняет роль snd_wl2 и snd_wl2 вообще не
                используется
             */
            if (((int32_t)(seq - psock->tcb.snd_wl1) > 0) || (ack_upd > 0) ||
               ((psock->tcb.snd_wl1 == seq) && (ack_upd == 0))) {
                uint32_t wnd = NTOH16(hdr->window);
#ifdef LIP_TCP_USE_WINSCALE
                if (psock->state_flags & SOCK_FLAGS_WS_EN) {
                    wnd <<= psock->snd_ws;
                }
#endif
                /* проверяем на duplicated ack  условия из определения dup ack - rfc5681 page 4 */
                if ((psock->tcb.snd_una != psock->tcb.snd_nxt) && /* a */
                    (pkt_seq_size == 0) && /* b,c */
                    (ack_upd == 0) && /* d */
                    (wnd == psock->tcb.snd_wnd)) /* e */ {

                    /** @todo когда будет SACK - надо проверить, что есть новая информация */
                    psock->dup_ack_cnt++;
                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                               "lip tcp: duplication ack %d\n", psock->dup_ack_cnt);

                    if (psock->state_flags & SOCK_FLAGS_FAST_RECOVERY) {
                        /* когда вошли в FastRecovery - увеличиваем cwnd на SMSS.
                         * rfc5681 4. p9 */
                        psock->cwnd = UINT32_ADD_SAT(psock->cwnd, psock->eff_snd_mss);
                    } else {
                        if (psock->dup_ack_cnt == LIP_TCP_DUP_ACK_THRESHOLD) {
                            /* rfc6582 page 5, 2) - посылаем FastRetransmit по 3-м
                             * повторным ack если ack подтверждает больше recov или условие
                             * из аункта 6.1 (cwnd > mss, ack - prev_ack <= 4*mss) */
                             /** @todo при timestamp - можно реализовать и условие 6.2*/
                            if (((int32_t)(psock->tcb.snd_una - psock->recover_seq) > 0)
                                    || ((psock->cwnd > psock->eff_snd_mss)
                                            && ((ack - psock->prev_ack) <= (unsigned)(psock->eff_snd_mss << 2)))) {

                                /* устанавливаем recover_seq (rfc6582 page 5, 2) */
                                psock->recover_seq = psock->tcb.snd_nxt;


                                /* rfc5681 p9 пункт 2. - устанавливаем ssthresh, при этом
                                 * во FlightSize не включаем пакеты, превышающие cwnd
                                 * из-за условия в rfc3042 */
                                uint32_t flight = MIN(psock->cwnd,
                                        psock->tcb.snd_nxt - psock->tcb.snd_una);
                                psock->ssthresh = MAX(flight/2, (uint32_t)2*psock->eff_snd_mss);

                                /* обновляем cwnd  (rfc5681 p9 пункт 3.) */
                                psock->cwnd = psock->ssthresh + 3*psock->eff_snd_mss;

                                lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                        "lip tcp: fast retransmit. ssthresh = %d, cwnd = %d\n",
                                        psock->ssthresh, psock->cwnd);
                                /* повторно передаем пакет */
                                send_retr = 1;

                                /* переходим в FastRecovery */
                                psock->state_flags |= SOCK_FLAGS_FAST_RECOVERY;
                            }
                        }
                    }
                } else if (ack_upd > 0) {
                    /* если пришел сегмент, который обновил часть данных, то
                     * сбрасываем счетчик повторных подтверждений */
                    psock->dup_ack_cnt = 0;
                    if (psock->state_flags & SOCK_FLAGS_FAST_RECOVERY) {

                        if ((int32_t)(ack - psock->recover_seq) >= 0) {
                            /* full ack (rfc6582 p5) - выходим из revovery
                             * и обновляем параметры */
                            uint32_t flight = psock->tcb.snd_nxt - psock->tcb.snd_una;

                            psock->cwnd = MIN(psock->ssthresh,
                                              MAX(flight, psock->eff_snd_mss) + psock->eff_snd_mss);
                            psock->state_flags &= ~(SOCK_FLAGS_FAST_RECOVERY
                                    | SOCK_FLAGS_PARTIAL_ACK);

                            lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                    "lip tcp: exit fast recovery\n");
                        } else {
                            /* partial ack (rfc6582 p6)  */
                            /* шлем повторный сегмент */
                            send_retr = 2;
                            /* уменьшаем cwnd */
                            psock->cwnd = psock->cwnd > (uint32_t)ack_upd ? psock->cwnd - ack_upd : 0;
                            if ((uint32_t)ack_upd >= psock->eff_snd_mss)
                                psock->cwnd += psock->eff_snd_mss;

                            if (!(psock->state_flags & SOCK_FLAGS_PARTIAL_ACK)) {
                                /* сбрасываем таймер только при первом partial ack */
                                psock->state_flags |= SOCK_FLAGS_PARTIAL_ACK;
                                psock->retr_time = lclock_get_ticks();
                            }

                            lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                    "lip tcp: partial ack received in fast recovery: ack num = %#x, recov = %#x, seg_cnt = %d\n",
                                    ack, psock->recover_seq, psock->retr_seg_cnt);
                        }
                    } else {
                        /* обновление таймера при подтверждении данных в соответствии
                         * с rfc6298 p5 пункт 5.3 */
                        psock->retr_time = lclock_get_ticks();

                        f_sock_upd_cwnd(psock, ack_upd);
                        /*когда не в FastRecovery - продвигаем recover_seq, чтобы  он
                          не отстал на цикл от текущей последовательности
                          (rfc6582  p10) */
                        if ((int32_t)(psock->prev_ack - psock->recover_seq) > 0) {
                            psock->recover_seq = psock->prev_ack;
                        }
                    }
                }


                /* обновление окна в соответствии с rfc793 page 72 */
                psock->tcb.snd_wnd = wnd;

                if (psock->zwnd_prob && (wnd > 0))
                    psock->zwnd_prob = 0;


                psock->tcb.snd_wl1 = seq;


                /* обновляем макс. окно, для оценки буфера другой стороны в алгоритме SWS */
                if (psock->max_snd_win < psock->tcb.snd_wnd)
                    psock->max_snd_win = psock->tcb.snd_wnd;

                psock->last_activ_tmr = lclock_get_ticks();
                psock->state_flags &= ~SOCK_FLAGS_IDLE;
#ifdef LIP_TCP_USE_KEEPALIVE
                psock->keepalive_cnt = 0;
#endif
            }
        }
    }

#ifdef LIP_TCP_USE_TIMESTAMP
    if ((res == LIP_ERR_SUCCESS) && ts_present) {
        /* обновляем ts_recent только в том случае, если
         * seg.seq <= last.ack.sent < seg.seq + seg.len
         * rfc1323 page 16 пункт 2 */
        if ((psock->last_ack_sent - seq) < pkt_seq_size) {
            psock->ts_recent = ts_val;
            psock->state_flags |= SOCK_FLAGS_TS_RECENT_VALID;
        }
    }
#endif


    /* если пакет для нас - обслуживаем, в зависимости от состояния сокета */
    if (res == LIP_ERR_SUCCESS) {
        switch (psock->state) {
            case LIP_TCP_SOCK_STATE_LISTEN:
                if (hdr_flags & LIP_TCP_FLAGS_SYN) {
                    f_proc_syn_packet(psock, hdr, &seq, data);

                    /* сохраняем информацию о удаленном сокете */
                    memcpy(&psock->rem_ip_info, info, sizeof(t_lip_ip_info));
                    /* ttl всегда по умолчанию - мб если нужно сделать опцией */
                    psock->rem_ip_info.ttl = 0;
                    psock->rem_ip_info.da = psock->rem_ip_addr;
                    psock->rem_ip_info.sa = info->da;
                    memcpy(psock->rem_ip_addr, info->sa, info->addr_len);
                    psock->rem_tcp_port = HTON16(hdr->src_port);

                    /* шлем в ответ ACK и SYN */
                    f_sock_send(psock, LIP_TCP_FLAGS_SYN | LIP_TCP_FLAGS_ACK, 0);

                    psock->state = LIP_TCP_SOCK_STATE_SYN_RECEIVED;

                    /* вместе с первым SYN могут в теории быть и данные
                       так что пробуем их принять.... */
                    f_sock_process_rx_data(sock_id, seq, data, data_size);
                }
                break;
#ifdef LIP_TCP_USE_ACTIVE_CONNECT
            case LIP_TCP_SOCK_STATE_SYN_SENT:
                /* если пришел syn */
                if (hdr_flags & LIP_TCP_FLAGS_SYN) {
                    psock->state = LIP_TCP_SOCK_STATE_SYN_RECEIVED;

                    f_proc_syn_packet(psock, hdr, &seq, data);
                    if (hdr_flags & LIP_TCP_FLAGS_ACK) {
                        /* шлем подтверждения SYN-пакета */
                        f_sock_send(psock, LIP_TCP_FLAGS_ACK, 0);
                    } else {
                        /* если только SYN пришел => одновременное открытие
                           шлем помимо ACK еще и SYN  rfc793 p32 figure 8 */
                        f_sock_send(psock, LIP_TCP_FLAGS_ACK | LIP_TCP_FLAGS_SYN,0);
                    }
                }
                /* далее те же действия, что и в SYN_RECEIVED
                   проверка ACK => нет break (!) */
#endif
                /* fallthrough */
            case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
                if (hdr_flags & LIP_TCP_FLAGS_ACK) {
                    /* проверяем, что получили ack на SYN
                       (данные в SYN_RECEIVED еще не шлем, так что подтверждение
                       должно совпадать с номером, что высылали последним)
                       если ack неверный - то уже отработаем в общей проверке ack
                       и пошлем RST
                       */
                    if (ack == psock->tcb.snd_nxt) {
                        psock->state = LIP_TCP_SOCK_STATE_ESTABLISHED;
                        /* когда входим в established состояние, устанавливаем переменные
                           в соответствии с rfc1122 4.2.2.20 (c),(f) page 94 */
                        psock->tcb.snd_wnd = NTOH16(hdr->window);
#ifdef LIP_TCP_USE_WINSCALE
                        if (psock->state_flags & SOCK_FLAGS_WS_EN) {
                            psock->tcb.snd_wnd <<= psock->snd_ws;
                        }
#endif
                        psock->tcb.snd_wl1 = seq;
                    }
                }

                /* до подтверждения SYN могут прийти данные - складываем их в буфер */
                f_sock_process_rx_data(sock_id, seq, data, data_size);
                break;
            case LIP_TCP_SOCK_STATE_ESTABLISHED:
            /* если мы закрыли сокет, то все равно можем принимать данные */
            case LIP_TCP_SOCK_STATE_FIN_WAIT1:
            case LIP_TCP_SOCK_STATE_FIN_WAIT2:
                f_sock_process_rx_data(sock_id, seq, data, data_size);
                /* обновляем указатель использованных данных, если подтвердили новые данные */
                if (ack_upd > 0) {
                    psock->snd_rdy -= ack_upd;
#if LIP_TCP_FIFO_MODE_ENABLE
                    if (psock->tx_mode == LIP_TCP_SOCK_MODE_FIFO) {
                        psock->snd_fifo.cons_ptr += ack_upd;
                        if (psock->snd_fifo.cons_ptr >= psock->snd_fifo.size)
                            psock->snd_fifo.cons_ptr -= psock->snd_fifo.size;
                    }
#endif
#if LIP_TCP_DD_MODE_ENABLE
                    if (psock->tx_mode == LIP_TCP_SOCK_MODE_DD) {
                        /* завершаем задания для данных по которым пришел ack */
                        uint32_t rem_ack_upd = ack_upd;
                        while ((psock->tx_busy_dd != 0) && (rem_ack_upd > 0)) {
                            t_lip_tcp_dd *tx_dd = &psock->tx_dds[psock->tx_cur_dd];
                            /* неподтвержденный размер в текущем задании */
                            t_lip_tcp_dd_size dd_rem = tx_dd->length - tx_dd->trans_cnt;
                            /* Прямое сложение trans_cnt и ack_upd может привести к
                             * переполнению trans_cnt, если размер dd меньше uint32,
                             * используем явное сравнение */
                            if (dd_rem <= rem_ack_upd) {
                                /* если весь подтвердили, то завершаем задание */
                                rem_ack_upd -= dd_rem;
                                tx_dd->trans_cnt = tx_dd->length;
                                if (tx_dd->cb != NULL) {
                                    tx_dd->cb(sock_id, tx_dd, LIP_TCP_DD_STATUS_DONE);
                                }
                                psock->tx_cur_dd = TX_DD_INC(psock->tx_cur_dd);
                                psock->tx_busy_dd--;
                            } else {
                                /* иначе только обновляем количество подтвержденных слов */
                                tx_dd->trans_cnt += rem_ack_upd;
                                rem_ack_upd = 0;
                            }
                        }
                    }
#endif
                }


                /* если уже послали FIN но не пришло подтверждение */
                if ((psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1) &&
                        (psock->state_flags & SOCK_FLAGS_FIN_SENT)) {
                    /* проверяем - пришло ли подтверждение FIN - если да - в состояние WAIT2 */
                    if (psock->tcb.snd_nxt == psock->tcb.snd_una)
                        psock->state = LIP_TCP_SOCK_STATE_FIN_WAIT2;
                }
                break;
            case LIP_TCP_SOCK_STATE_CLOSING:
                if (psock->state_flags & SOCK_FLAGS_FIN_SENT) {
                    /* проверяем - пришло ли подтверждение FIN */
                    if (psock->tcb.snd_nxt == psock->tcb.snd_una) {
                        psock->state = LIP_TCP_SOCK_STATE_TIME_WAIT;
                        psock->wait_time = lclock_get_ticks();
                    }
                }
                break;
            case LIP_TCP_SOCK_STATE_LAST_ACK:
                /* пришло подтверждение FIN */
                if (psock->state_flags & SOCK_FLAGS_FIN_SENT) {
                    if (psock->tcb.snd_nxt == psock->tcb.snd_una) {
                        f_sock_set_closed(sock_id);
                    }
                }
                break;
        }

        /* приняли FIN */
        if (hdr_flags & LIP_TCP_FLAGS_FIN) {
            /* в listen, syn_sent и closed - просто выкидываем */
            if ((psock->state != LIP_TCP_SOCK_STATE_LISTEN)
                    && (psock->state != LIP_TCP_SOCK_STATE_SYN_SENT)
                    && (psock->state != LIP_TCP_SOCK_STATE_CLOSED)) {

                /* определяем номер последовательности, который соответствует FIN.
                 * FIN всегда соответствует последнему номеру в пакете.
                 * Обрабатываем FIN, только если до него все уже обработали,
                 * иначе у нас есть непринятые дыры и мы не изменяем состояние сокета
                 * до их заполнения */
                /** @note сейчас FIN с данными не подряд просто откидывается
                 *  и исходим из того, что так как он не подтвержден, то
                 *  будет повторен в дальнейшем. Иначе потребуется сохранять
                 *  seq для FIN с флагом и учитывать это при приеме последующих данных */
                const uint32_t fin_seq = pkt_seq + pkt_seq_size - 1;
                if (fin_seq == psock->tcb.rcv_nxt) {
                    /* обновляем состояние */
                    switch (psock->state) {
                        case LIP_TCP_SOCK_STATE_FIN_WAIT1:
                            psock->state = LIP_TCP_SOCK_STATE_CLOSING;
                            break;
                        case LIP_TCP_SOCK_STATE_FIN_WAIT2:
                        case LIP_TCP_SOCK_STATE_TIME_WAIT:
                            psock->state = LIP_TCP_SOCK_STATE_TIME_WAIT;
                            psock->wait_time = lclock_get_ticks();
                            break;
                        case LIP_TCP_SOCK_STATE_ESTABLISHED:
                        case LIP_TCP_SOCK_STATE_SYN_RECEIVED:
                            psock->state = LIP_TCP_SOCK_STATE_CLOSE_WAIT;
                            break;
                    }

                    /* LIP_TCP_FLAGS_FIN требует тоже подтверждение */
                    psock->tcb.rcv_nxt++;
                    psock->state_flags |= SOCK_FLAGS_NEED_ACK;

#if LIP_TCP_DD_MODE_ENABLE
                    /* по приему fin в режиме запросов завершаем все запросы на прием */
                    f_sock_abort_all_dd(sock_id, LIP_TCP_DD_STATUS_CON_CLOSED, 0);
#endif
                } else {
                    /* посылка ACK на непоследовательные данные */
                    psock->state_flags |= SOCK_FLAGS_NEED_ACK;
                }
            }
        }



        if (send_retr) {
            /** @todo можно сделать передачу 2-х сегментов, как модификация NewReno
                но мб делать это только когда будет SACK */
            f_sock_send(psock, LIP_TCP_SENDFLAGS_RETRANS | LIP_TCP_FLAGS_ACK, 0);
        }
    } /* if (res == LIP_ERR_SUCCESS)  */



    /* шлем немедленное подтверждение без данных в случаях, когда
       не должны откладывать ack (out-of-order сегмент etc) */
    if (psock->state_flags & SOCK_FLAGS_NEED_ACK) {
        f_sock_send(psock, LIP_TCP_FLAGS_ACK, 0);
    }

    return res;
}



LINLINE static int f_cmp_ipinfo(const t_lip_ip_info *sock_info, const t_lip_ip_info *pkt_info) {
    return (sock_info->type == pkt_info->type)
            && (sock_info->addr_len == pkt_info->addr_len)
            && !memcmp(sock_info->da, pkt_info->sa, pkt_info->addr_len);
}


/***********************************************************************************
 *  Обработка входящего TCP-пакета. Вызывается ядром стека, после обработки в ip
 *  Проверяется контрольная сумма и ищется нужный сокет,
 *  который может обработать данный пакет
 ***********************************************************************************/
t_lip_errs lip_tcp_process_packet(const t_lip_ip_rx_packet *ip_packet) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (ip_packet->pl.size < sizeof(t_lip_tcp_hdr)) {
        err = LIP_ERR_RXBUF_TOO_SMALL;
    } else {
        const t_lip_tcp_hdr *hdr = (const t_lip_tcp_hdr*)ip_packet->pl.data;

        /* проверка контрольной суммы, если не была проверена аппаратно */
        if (!(ip_packet->eth_packet->ll_frame->info.ll_flags & LIP_LL_RXFLAGS_TRANSP_CHECKSUM_CHECKED)) {
            t_lip_tcp_chk_hdr chk_hdr;
            uint16_t chk_sum;

            /* заполняем псевдозаголовок для вычисления контрольной суммы */
            memcpy(chk_hdr.dst_addr, ip_packet->ip_info.da, ip_packet->ip_info.addr_len);
            memcpy(chk_hdr.src_addr, ip_packet->ip_info.sa, ip_packet->ip_info.addr_len);
            chk_hdr.zero = 0;
            chk_hdr.protocol = ip_packet->ip_info.protocol;
            chk_hdr.length = HTON16(ip_packet->pl.size);

            /* вычисляем сумму сначала по псевдозаголовку, затем по TCP-пакету */
            chk_sum = lip_chksum(0, (uint8_t*)&chk_hdr, LIP_TCP_CHK_HDR_SIZE);
            chk_sum = lip_chksum(~chk_sum, (const uint8_t*)hdr, ip_packet->pl.size);

            if (chk_sum!=0xFFFF) {
                err = LIP_ERR_TCP_CHECKSUM;
            }
        }



        if (err == LIP_ERR_SUCCESS) {
            int proc_sock = -1;

            const uint16_t dst_port = NTOH16(hdr->dest_port);
            const uint16_t src_port = NTOH16(hdr->src_port);


            for (int i = 0; (i < LIP_TCP_SOCKET_CNT) && (proc_sock < 0); ++i) {
                t_lip_tcp_sock* psock = &f_tcp_sock_list[i];
                if ((psock->tcp_port == dst_port) && (psock->rem_tcp_port == src_port) &&
                        (psock->state != LIP_TCP_SOCK_STATE_NOT_IN_USE) &&
                        (psock->state != LIP_TCP_SOCK_STATE_CLOSED) &&
                        (psock->state != LIP_TCP_SOCK_STATE_LISTEN) &&
                        f_cmp_ipinfo(&psock->rem_ip_info, &ip_packet->ip_info)) {

                    /* для удаленного соединения сравниваем удаленный
                     * порт и адрес */
                    proc_sock = i;
                }
            }

            /* если не нашлось сокета с теми же параметрами удаленного хоста,
                но есть сокет, слушающий данный порт, то передаем пакет ему */
            if (proc_sock < 0) {
                int listen_sock_cmp_lvl = -1;
                int listen_sock = -1;
                for (int i = 0; (i < LIP_TCP_SOCKET_CNT) && (proc_sock < 0); ++i) {
                    t_lip_tcp_sock* psock = &f_tcp_sock_list[i];
                    if ((psock->tcp_port == dst_port) &&
                            (psock->state == LIP_TCP_SOCK_STATE_LISTEN)) {

                        /* Находим первый найденный сокет, слушающий данный порт
                           (на случай, если этот пакет - активное открытие удаленным
                            хостом нового соединения) */
                        int cmp_lvl = 0;
                        /* если задан удаленный порт или адрес для слушающего сокета,
                         * то проверяем их. cmp_lvl указывает степень совпадения,
                         * чтобы выбрать по возможности наиболее подходящий сокет из
                         * слушающих, в первую очередь с указанными удаленными параметрами,
                         * а не с любыми (rfc793 p.3) */
                        if (psock->state_flags & SOCK_FLAGS_FIXED_REM_PORT) {
                            if (psock->rem_tcp_port == HTON16(hdr->src_port)) {
                                cmp_lvl += 2;
                            } else {
                                cmp_lvl = -1;
                            }
                        }
                        if ((cmp_lvl >= 0) && (psock->state_flags & SOCK_FLAGS_FIXED_REM_IP)) {
                            if (f_cmp_ipinfo(&psock->rem_ip_info, &ip_packet->ip_info)) {
                                cmp_lvl++;
                            } else {
                                cmp_lvl = -1;
                            }
                        }

                        if (cmp_lvl > listen_sock_cmp_lvl) {
                            listen_sock = i;
                            listen_sock_cmp_lvl = cmp_lvl;
                        }
                    }
                }

                proc_sock = listen_sock;
            }


            if (proc_sock >= 0) {
                lip_tcp_sock_process_packet(SOCK_ID_BY_IDX(proc_sock),
                                            ip_packet->pl.data, ip_packet->pl.size,
                                            ip_packet);
            } else {
                /* если нет сокета, для которого предназначено это сообщение - посылаем RST */
                f_send_unspec_sock_rst(hdr, &ip_packet->ip_info, ip_packet->pl.size);
            }
        }
    }
    return err;
}


/*******************************************************************************
 *  выполнение фоновых задач ip-стека:
 *   - передача буферизированных данных
 *   - повторная передача по таймауту
 *   - посылка keepalive и zero win probe
 *******************************************************************************/
void lip_tcp_pull(void) {
    t_lclock_ticks cur_time = lclock_get_ticks();

    /* обновляем iss, в соответствии с rfc1122 он должен быть на основе клока
     * увеличиваем раз в 4 мкс */
    f_cur_iss+= ((cur_time - f_retr_check_time) << 8);


    f_retr_check_time = cur_time;

    for (int i = 0; (i < LIP_TCP_SOCKET_CNT); ++i) {
        t_lip_tcp_sock *psock = &f_tcp_sock_list[i];
        if ((psock->state != LIP_TCP_SOCK_STATE_NOT_IN_USE) &&
            (psock->state != LIP_TCP_SOCK_STATE_CLOSED)) {

            if (psock->state == LIP_TCP_SOCK_STATE_TIME_WAIT) {
                /* если сокет находится в time_wait - то по таймауту
                 * можем перевести его в состояние closed */
                if ((f_retr_check_time - psock->wait_time) >
                    LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_TIME_WAIT)) {
                    f_sock_set_closed(SOCK_ID_BY_IDX(i));
                }
            } else {
                 /************** проверка retransmit ****************************/
                 /* Так как передаем повторно только самый первый неподтвержденный
                    сегмент, то достаточно проверить, что текущее окно не равно 0,
                    чтобы убедится, что передаваемый сегмент находится хотя бы
                    частично в окне для принимающей стороны. При нулевом окне
                    повторная передача будет вне окна и другая сторона не должна
                    принимать сегмент и нет смысла его передавать */
                if ((psock->retr_seg_cnt != 0) && (psock->tcb.snd_wnd != 0)) {
                    /* если есть сегменты на повторную передачу и таймаут истек */
                    if ((f_retr_check_time - psock->retr_time)  > psock->rto) {
                        /* при превышении количества повторных передач
                         * уровня LIP_TCP_RETRANS_MAX_CNT - разрываем соединение */
                        if (psock->retr_cnt > LIP_TCP_RETRANS_MAX_CNT) {
                            lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_WARNING_LOW, 0,
                                    "lip tcp: socket %d is abortet by retransmit limit\n", i);
                            lsock_abort(SOCK_ID_BY_IDX(i));
                        } else {
                            /* если лимит не превысили - выполняем повторную передачу */
                            lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                    "lip tcp: retransmit timeout. retr_cnt = %d, rto = %d, seg cnt = %d, tout = %d, srtt = %d, rttvar = %d\n",
                                    psock->retr_cnt, psock->rto, psock->retr_seg_cnt,
                                    f_retr_check_time - psock->retr_time, psock->srtt, psock->rttvar);
                            f_sock_send(psock, LIP_TCP_SENDFLAGS_RETRANS, 0);

#ifdef LIP_TCP_USE_SLOW_START
                            if (psock->retr_cnt == 0) {
                                /* в случае первой повторной передачи сегмента меняем cwnd и ssthres
                                 * в соответствии с rfc5681 (4) page 7
                                 */
                                uint32_t flight_size = psock->tcb.snd_nxt - psock->tcb.snd_una;
                                psock->ssthresh = MAX((flight_size/2), (uint32_t)2*psock->eff_snd_mss);
                            }
                            /* cwnd устанавливаем равным loss_windows = 1*mss
                             * (rfc5681 p.8) */
                            psock->cwnd = psock->eff_snd_mss;
#endif

                            psock->retr_cnt++;
                            /* увеличиваем время RTO в 2 раза (rfc6298 5.5 p5), но не больше максимального */
                            psock->rto <<= 1;
                            if (psock->rto > LIP_TCP_RTO_MAX)
                                psock->rto = LIP_TCP_RTO_MAX;
                            /* перезапуск таймера (rfc6298 5.6 p5) */
                            psock->retr_time = cur_time;


                            /* rfc6582 - по RTO выходим из fast recovery и
                             * сохраняем наибольший переданный SN */
                            psock->state_flags &= ~(SOCK_FLAGS_FAST_RECOVERY |
                                    SOCK_FLAGS_PARTIAL_ACK);
                            psock->recover_seq = psock->tcb.snd_nxt;
                        }
                    }
                }


                /* в состояниях, в которых мы можем еще передавать данные
                   проверяем необходимость и возможность их передачи и передаем */
                if ((psock->state == LIP_TCP_SOCK_STATE_ESTABLISHED)
                        || (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1)
                        || (psock->state == LIP_TCP_SOCK_STATE_CLOSING)
                        || (psock->state == LIP_TCP_SOCK_STATE_LAST_ACK)) {

                    if (psock->snd_rdy != 0) {
                        /* смотрим - сколько передано, но не подтверждено */
                        uint32_t una = psock->tcb.snd_nxt - psock->tcb.snd_una;
                        /* смотрим - сколько есть готовых не переданных данных */
                        int rdy = psock->snd_rdy - una;
                        /* смотрим - сколько можем послать по окну */
                        uint32_t wnd = psock->tcb.snd_wnd > una ?
                                psock->tcb.snd_wnd - una : 0;

#ifdef LIP_TCP_USE_SLOW_START
                        /* проверяем условие, что мы не превысили так же cwnd */
                        uint32_t cwnd = psock->cwnd > una ?
                            psock->cwnd - una : 0;

                        /* На первые 2 дублированных ACK (пока не вошли в FAST_RECOVERY)
                         * можем передать по сегменту больше, чем cwnd (rfc3042 & rfc5681 1. p9) */
                        if ((psock->dup_ack_cnt != 0) && !(psock->state_flags
                                                            & SOCK_FLAGS_FAST_RECOVERY)) {
                            cwnd += psock->eff_snd_mss * MIN(LIP_TCP_LIM_TRANS_SEG_CNT, psock->dup_ack_cnt);
                        }

                        if (wnd > cwnd)
                            wnd = cwnd;
#endif

                        if (rdy > 0) {
                            int send_size = 0;
                            uint32_t wrdy = MIN((unsigned)rdy, wnd);
                            /* Проверяем условия отправки в соответствии с RFC1122
                             * 4.2.3.4 page 99.
                             *  сейчас пункты 2-4 выполняются как если PUSH
                             * всегда установлен. Используем вариант, когда
                             * стек сам определяет, когда нужно ставить PUSH */

                             /* 1. min(D, U) >= eff. send. mss */
                            if (wrdy >= psock->eff_snd_mss) {
                                send_size = psock->eff_snd_mss;
                            } else {
                                /* признак - выполняется ли условие алгоритма Nagle */
                                int nagle = psock->opt & LIP_TCP_SOCK_OPT_NAGLE_DISABLE ? 1 :
                                        psock->tcb.snd_nxt == psock->tcb.snd_una ? 1: 0;

                                /* 2. [snd.nxt == snd.una] PUSHED and D <= U */
                                if (nagle && (rdy <= (int)wnd)) {
                                    send_size = rdy;
                                } else if (nagle && (wrdy >= (psock->max_snd_win >> 1))) {
                                    /* 3. [snd.nxt == snd.una] min(U,D) >= Fs*Max(snd.wnd) */
                                    send_size = wrdy;
                                } else if ((cur_time - psock->last_activ_tmr) >
                                            LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_SWS_PUSH_TOUT)) {
                                    /* 4. PUSHed and override timer occur */
                                    send_size = wrdy;
                                }
                            }



                            if (send_size > 0) {
                                uint32_t flags = LIP_TCP_FLAGS_ACK;
                                /* флаг PSH устанавливаем, если данных в буфере больше нет */
                                if (send_size == rdy)
                                    flags |= LIP_TCP_FLAGS_PSH;
                                f_sock_send(psock, flags, send_size);
                            } else if (psock->tcb.snd_wnd == 0) {
                                /* zero windows probe */
                                uint32_t zero_probe_tout = MIN(psock->rto << psock->zwnd_prob, LIP_TCP_RTO_MAX);
                                if ((f_retr_check_time - psock->last_activ_tmr) > zero_probe_tout) {

                                    if (psock->zwnd_prob < LIP_TCP_ZERO_WIND_PROB_INC_CNT)
                                        psock->zwnd_prob++;
                                    lip_printf(LIP_LOG_MODULE_TCP, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                                               "lip tcp: zero windows probe\n");
                                    /* для winprobe используем пакет, аналогичный keepalive,
                                     * как в стеке linux, что позволяет не использовать
                                     * передачу одного байта и не отслеживать его повторные передачи и т.п. */
                                    f_sock_send(psock, LIP_TCP_SENDFLAGS_KEEPALIVE | LIP_TCP_FLAGS_ACK, 0);
                                }
                            }
                        }
                    } else {
                        /* если все послали и нужен FIN - посылаем */
                        if (psock->state_flags & SOCK_FLAGS_NEED_FIN) {
                            if (f_sock_send(psock, LIP_TCP_FLAGS_ACK | LIP_TCP_FLAGS_FIN, 0)==0) {
                                /* устанавливаем флаг что отослан и нужно ждать подтверждение */
                                psock->state_flags &= ~SOCK_FLAGS_NEED_FIN;
                                psock->state_flags |= SOCK_FLAGS_FIN_SENT;
                            }
                        }
                    }
                } /* if state - establ, fin_wait1, last_ack */


                /* если установлен флаг, что нужно послать подтверждение
                 * немедленно или что нужно послать отложенное подтверждение
                 * и таймаут истек, то посылаем ACK */
                if (((psock->state_flags & SOCK_FLAGS_DELAYED_ACK)
                            && ((cur_time - f_delayed_ack_tout) >
                               LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_DELAYED_ACK_TIME))) ||
                        (psock->state_flags & SOCK_FLAGS_NEED_ACK)) {
                    if ((psock->state == LIP_TCP_SOCK_STATE_ESTABLISHED)
                            || (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1)
                            || (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT2)) {
                        f_sock_send(psock, LIP_TCP_FLAGS_ACK, 0);
                    }
                }

#ifdef LIP_TCP_USE_KEEPALIVE
                /******************************************************************
                 * если опция разрешена, то в состояниях, в которых мы можем
                 * бесконечно долго находится без ответа другой стороны
                 * проверяем и шлем по таймауту keepalive пакет
                 ******************************************************************/
                if ((psock->opt & LIP_TCP_SOCK_OPT_KEEPALIVE) && (psock->retr_seg_cnt==0)) {
                    if ((psock->state == LIP_TCP_SOCK_STATE_ESTABLISHED)
                        || (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1)
                        || (psock->state == LIP_TCP_SOCK_STATE_FIN_WAIT1)) {
                        /* по истечению заданного времени LIP_TCP_KEEPALIVE_TOUT
                         * в неактивном состоянии шлем keepalive пакет,
                         * который в дальнейшем будут повторно передаваться
                         * раз с интервалом LIP_TCP_KEEPALIVE_INTERVAL,
                         * пока не получим подтверждение или не достигнем
                         * LIP_TCP_KEEPALIVE_PROBES повторных передач
                         */
                        t_lclock_ticks keepalive_tout = psock->keepalive_cnt == 0 ?
                                    LIP_TCP_KEEPALIVE_TOUT : LIP_TCP_KEEPALIVE_INTERVAL;

                        if ((cur_time - psock->last_activ_tmr)
                                > LTIMER_MS_TO_CLOCK_TICKS(keepalive_tout)) {
                            /* если превысили заданное время - разрываем соединение */
                            if (psock->keepalive_cnt >= LIP_TCP_KEEPALIVE_PROBES) {
                                lsock_abort(SOCK_ID_BY_IDX(i));
                            } else {
                                /* иначе - шлем еще один keepalive пакет */
                                f_sock_send(psock, LIP_TCP_SENDFLAGS_KEEPALIVE | LIP_TCP_FLAGS_ACK, 0);
                                psock->keepalive_cnt++;
                            }
                        }
                    }
                }
#endif
                /**************************************************************
                 * В соответствии с rfc5681 4.1 page 10-11 - если сокет
                 * не принимал и не передавал ничего за время rto, то нужно
                 * уменьшить cwnd до restart window.
                 * При этом мы не можем тут использовать cur_time, а должны
                 * использовать именно текущее время, а не время на начало функции,
                 * т.к. last_activ_tmr может обновится после получения cur_time
                 **************************************************************/
                if (!(psock->state_flags & SOCK_FLAGS_IDLE) &&
                        ((lclock_get_ticks() - psock->last_activ_tmr) > psock->rto)) {
                    uint32_t iw = f_sock_get_iw(psock);
                    psock->cwnd = MIN(iw, psock->cwnd);
                    psock->state_flags |= SOCK_FLAGS_IDLE;
                }
#ifdef LIP_TCP_USE_TIMESTAMP
                /* сбрасываем флаг действительности TS_RECENT в соответствии
                 * с rfc1323 p22. При этом нельзя проверять только при приеме,
                 * так как используем 32-битный клок и если активности не было
                 * больше LIP_TCP_PAWS_INVALID_TIME то эта проверка также
                 * не пройдет, как и по timestamp => нужно проверять периодически */
                if ((psock->state_flags & SOCK_FLAGS_TS_RECENT_VALID)
                    && ((cur_time - psock->last_activ_tmr) >
                        LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_PAWS_INVALID_TIME))) {
                    psock->state_flags &= ~SOCK_FLAGS_TS_RECENT_VALID;
                }
#endif
            } //if (state != TIME_WAIT) && (state != CLOSED)

            if ((psock->state_flags & SOCK_FLAGS_CLOSE_TOUT) &&
                    (psock->state != LIP_TCP_SOCK_STATE_CLOSED) &&
                    ((cur_time - psock->close_time) > psock->close_tout)) {
                lsock_abort(SOCK_ID_BY_IDX(i));
            }
        } //if sock in use
    } //for (i=0; (i < LIP_TCP_SOCKET_CNT); ++i)


    /* обновляем таймер для отложенных подтверждений */
    if ((cur_time - f_delayed_ack_tout) > LTIMER_MS_TO_CLOCK_TICKS(LIP_TCP_DELAYED_ACK_TIME)) {
        f_delayed_ack_tout = cur_time;
    }
}


/***************************************************************************
* Функция вызывается при смене текущего IP-адреса
* Необходимо оборвать все соединения, использующие этот адрес
***************************************************************************/
void lip_tcp_ip_addr_changed(t_lip_ip_type type, const uint8_t *prev_addr, int len) {
    int i;
    for (i = 0; i < LIP_TCP_SOCKET_CNT; ++i) {
        if ((f_tcp_sock_list[i].state  > LIP_TCP_SOCK_STATE_LISTEN) &&
                (f_tcp_sock_list[i].state <= LIP_TCP_SOCK_STATE_CLOSE_WAIT)) {

            if ((f_tcp_sock_list[i].rem_ip_info.type == type)
                 && (!memcmp(f_tcp_sock_list[i].rem_ip_info.sa, prev_addr, len))) {
                lsock_abort(SOCK_ID_BY_IDX(i));
            }
        }
    }
}
#endif

