/***************************************************************************//**
  @file ltrsrv_eth.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   05.02.2012

  Файл содержит логику работы с крейстом по интерфейсу Ethernet (на данный момент
  это только LTR030/LTR031).
  Интерфейс реализован на Беркли-сокетах, с использованием общей процедуры
  ожидания всех сокетв из ltrsrv_wait.c.

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

  Кроме того, файл задержит логику управления записями со статическими IPv4
    адресами крейтов (добавление/удаление/изменение) и подключением/отключением
    крейтов по этим статическим адресам
  *****************************************************************************/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "lcspec.h"


#include "ltrsrv_defs.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_cfg.h"
#include "ltrsrv_log.h"
#include "ltrsrv_crates.h"
#include "ltrsrv_eth.h"

#include "ltimer.h"

#ifdef _WIN32
    #include <iphlpapi.h>
#else
#ifdef HAVE_IFADDRS_H
    #include <ifaddrs.h>
#else
    #include <getifaddrs.h>
#endif
    #include <errno.h>
#endif

#include <stddef.h>
#include <stdio.h>
#include <string.h>




/* проверка провильности описателя крейта */
#define _ETH_CHECK_HND(hnd) (hnd) == NULL ? LTRSRV_ERR_CRATE_HANDLE : \
    (hnd)->intf_data == NULL ? LTRSRV_ERR_CRATE_INTF_CLOSED : \
    (hnd)->par.intf != LTR_CRATE_IFACE_TCPIP ? LTRSRV_ERR_CRATE_INVALID_INTF : 0


#define LTR_TCP_DATA_PORT        11110
#define LTR_TCP_CTRL_PORT        11113

#define LTR_TCP_DATA_RECON_LIMIT  3

#define LTR_ETH_CTRL_SIG          0x314C5443 /* "CTL1" */


#define LTR_CRATE_FPGA_INFO_SIZE_MIN   250 /* минимальный размер информации о FPGA,
                                              который должен быть получен от крейта */
#define LTR_CRATE_FPGA_INFO_SIZE_MAX   255 /* максимальный размер информации о FPGA*/

/* Коды команд для Ethernet */
#define LTR_ETH_CMD_INIT         0x80000000
#define LTR_ETH_CMD_NOP          0x00000000
#define LTR_ETH_CMD_GET_NAME     0x00000001
#define LTR_ETH_CMD_GET_ARRAY    0x00000002
#define LTR_ETH_CMD_PUT_ARRAY    0x00000003
#define LTR_ETH_CMD_POLL_STAT    0x00000004


#define LTR_PROTOCOL_VERSION_MAGIC          0xD91D3B45
#define LTR_PROTOCOL_VERSION_MAGIC_POS      504


/* Запрос по управляющему каналу TCP */
typedef struct {
    uint32_t Signature;        /* == LTR_IP_CTRL_SIG */
    uint32_t CmdCode;          /* Код команды */
    uint32_t CmdParam;         /* Параметр команды */
    uint32_t VarDataLen;       /* Длина данных переменной длины */
    uint32_t ExpectReplyLen;   /* Сколько байт ответа ожидается */
    uint8_t data[1];
} t_eth_ctl_req;


/* Ответ крейта по управляющему каналу TCP */
typedef struct {
    uint32_t Signature;        /* == LTR_IP_CTRL_SIG */
    uint32_t ResultCode;       /* Код завершения */
    uint32_t VarDataLen;       /* Длина данных переменной длины */
    uint8_t data[1];
} t_eth_ctl_reply;



static uint8_t f_rcv_buf[LTRSRV_ETH_RCV_BUF_SIZE];
static int f_eth_nodelay = 0;


#define ETH_CTL_REQ_HDR_SIZE (offsetof(t_eth_ctl_req, data))
#define ETH_CTL_REPLY_HDR_SIZE (offsetof(t_eth_ctl_reply,data))


//#define LTR_CRATE_IP_ADDR_SIZE 16

typedef enum {
    _IP_ENTRY_STFLAG_SAVE    = 1, /** признак, что эту запись нужно
                                                  сохранять в файл */
    _IP_ENTRY_STFLAG_NOT_USE = 2 /** флаг, что эту запись не нужно
                                                   использовать в текущей сессии.
                                                   Она только для сохранения */
} t_ip_entry_stflags;

typedef struct {
    uint32_t ip;
    uint32_t flags;
    uint32_t save_flags;
    int st_flags; /* флаги из t_ip_entry_stflags */
    t_ltimer recon_tmr; /* таймер для переподключения */
    unsigned recon_cnt;
    t_ltr_crate* crate;
    t_ltr_crate_ip_status status;
} t_ltr_crate_ip_entry;

/* состояние автомата инициализации крейта */
typedef enum {
    ETH_INIT_STATE_CTL_CH_CONN,
    ETH_INIT_STATE_INITIALIZATION,
    ETH_INIT_STATE_DATA_CH_CONN,
    ETH_INIT_STATE_DONE
} t_eth_init_state;

/* состояние обработки команду по управлящему каналу */
typedef enum {
    ETH_CMD_STATE_IDLE          = 0, /* нет обрабатываемой команду */
    ETH_CMD_STATE_WAIT_SEND_BUF = 1, /* передаем команду крейту */
    ETH_CMD_STATE_WAIT_RECV_BUF = 2, /* принимаем ответ от крейта */
    ETH_CMD_STATE_ERROR         = 3  /* принят ответ с ошибкой */
} t_cmd_state;

/* дополнительные флаги о состоянии интерфейса */
typedef enum {
    ETH_FLAGS_DATA_SOCK_RECONNECTION   = 1 << 0, /* идет повторное установление связи по данным */
    ETH_FLAGS_DATA_SOCK_CHECK_SEC_CON  = 1 << 1, /* проверка, не закрыто ли соединение данных из-за
                                                    перевода соединения во вторичное состояние */
} t_eth_flags;


#ifdef LTRD_ETH_DUMP_ENABLED
    #define DUMP_PACKET_CNT         4096
    #define DUMP_PACKET_MAX_SIZE    4096

    #define DUMP_NEXT_POS(pos) do { \
        if (++pos==DUMP_PACKET_CNT) \
            pos = 0; \
        } while(0);

    typedef struct {
        time_t time;
        uint8_t dir;
        uint32_t size;
        uint32_t snd_req;
        uint8_t data[DUMP_PACKET_MAX_SIZE];
    } t_dump_packet;
#endif

/* состояние Ethernet-интфейса */
typedef struct {
    t_socket cmd_sock, data_sock; /* сокеты для передачи динных и команд */
    t_eth_init_state state; /* состояние процесса инициализации крейта */
    t_cmd_state cmd_st; /* состояние выполнения управляющей команду */
    int flags; /* флаги состояния интерфейса из t_eth_flags*/

    t_eth_ctl_req* req; /* буфер, выделенный под управляющий запрос */
    t_eth_ctl_reply * reply; /* буфер, выделенный под ответ */
    uint32_t req_size; /* размер буфера, выделенного под запрос */
    uint32_t reply_size; /* размер буфера, выделенного под ответ */

    uint32_t cur_pos; /* текущая позиция для приема/передачи данных */
    t_ltimer cmd_tmr; /* таймер для определения времени выполнения команды */


    t_ltr_crate_cmd_cb proc_cmd_cb; /* callback верхнего уровня на завершение
                                       выполнение команды */
    void* proc_data;  /* указатель, который передается proc_cmd_cb */
    uint32_t *proc_rx_size; /* указатель для сохранения размера принятого буфера */
    uint8_t *proc_buf; /* буфер, в который принимается ответ от команды */
    t_ltr_crate_snd_rdy_cb snd_rdy_cb; /* callback, вызываемый когда сокет с данными
                                          будет готов на передачу */
    int rcv_data_offs; /* смещение при приеме данных (0-3) - используется для указания
                          что был сохранен хвост не кратный 4 */
    int rcv_last_wrd; /* сюда сохраняется не кратный 4-м хвост */
    int data_recon_cnt; /* количество попыток повторить соединение */

    /* данные команды, выполняемой в рабочем режиме через очередь команд */
    struct {
        uint32_t recvd_size;
        uint8_t  recvd_data[32];
    } work_cmd_ctx;

#ifdef LTRD_ETH_DUMP_ENABLED
    uint32_t dump_pos;
    t_dump_packet dump[DUMP_PACKET_MAX_SIZE];
#endif
} t_eth_data;


static void f_ctl_cb(t_socket sock, int event, void *data);
static void f_data_cb(t_socket sock, int event, void *data);
static void f_intf_close(t_ltr_crate *crate, int close_by_err);
static void f_check_intf(void);
static void f_intf_check_tout_cb(t_socket sock, int event, void *data);
static void f_check_recon_cb(t_socket sock, int event, void *data);

static int  f_start_data_recon(t_ltr_crate *crate);
static void f_data_reconnection_finish(t_ltr_crate *crate);


static t_ltr_crate_ip_entry f_ip_entries[LTRSRV_ETH_STATIC_IP_ENTRY_CNT];
static int f_ip_cnt = 0;

#ifdef WIN32
/* список адресов хоста. один - для текущий, другой - с предыдущего прохода,
 * для обнаружения новых адресов */
static PIP_ADAPTER_INFO f_hostaddrs[2] = {NULL, NULL};
#else
static struct ifaddrs *f_hostaddrs = NULL;
#endif
static uint32_t f_eth_poll_time = LTRSRV_ETH_CRATE_POLL_INTERVAL;
static uint32_t f_eth_con_timeout = LTRSRV_ETH_CONNECTION_TOUT;
static uint32_t f_eth_cmd_timeout = LTRSRV_ETH_CTL_CMD_TOUT;
static uint32_t f_eth_intf_check_time = LTR_SERVER_DEFAULT_ETH_INTF_CHECK_TIME;
static uint32_t f_eth_reconnect_time = LTRSRV_ETH_RECONNECT_TOUT;




static t_ltrsrv_str_tbl f_ethcmd_str[] = {
    {LTR_ETH_CMD_INIT,      "Init"},
    {LTR_ETH_CMD_NOP,       "Nop"},
    {LTR_ETH_CMD_GET_NAME,  "Get Name"},
    {LTR_ETH_CMD_GET_ARRAY, "Get Array"},
    {LTR_ETH_CMD_PUT_ARRAY, "Put Array"},
    {LTR_ETH_CMD_POLL_STAT, "Poll status"}
};

#define LOGSTR_CMD_CODE(cmd) ltrsrv_log_get_str(cmd, f_ethcmd_str, sizeof(f_ethcmd_str)/sizeof(f_ethcmd_str[0]))


static int f_sock_set_nodelay(t_socket s, int nodelay) {
    int err = LTRSRV_ERR_SUCCESS;
    if (L_SOCKET_ERROR == setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay)))
        err = LTRSRV_ERR_SOCKET_SET_NODELAY;
    return err;
}




/******************************************************************************
  Функция запускает подключение по сокету.
  Если подключение сейчас не может быть выполнено, то добавляет сокет в лист
    ожидания с использованием f_ctl_cb и data в качестве данных.
    В этом случае функция завершается без ошибок и в wt возвращает 1
  Если подключение завершилось сразу, то функция в wt возвращает 0
  Если соединение завершилось с ошибкой, то возвращает отрицательный код ошибки
 *******************************************************************************/
static int f_sock_start_conn(t_socket* psock, int family, const char* ip_addr,
                             uint16_t port, int* wt, void* data, t_ltrsrv_wait_cb cb) {
    t_socket s = L_INVALID_SOCKET;
    struct sockaddr_in peer;
    int err = 0;

    if (family != AF_INET)
        err = LTRSRV_ERR_SOCKET_ADDR_FAMILY;

    if (!err) {
        s = socket(family, SOCK_STREAM, IPPROTO_TCP);
        if (s == L_INVALID_SOCKET)
            err = LTRSRV_ERR_SOCKET_CREATE;
    }

    /* Переводим сокет в неблокирующий режим работы */
    if (!err) {
        SOCK_SET_NONBLOCK(s, err);
    }

    /* устанавливаем размеры буферов сокета */
#ifdef LTRD_ETH_CRATE_SOCK_SEND_BUF_SIZE
    if (!err) {
        uint32_t buf_size = LTRD_ETH_CRATE_SOCK_SEND_BUF_SIZE;
        if (L_SOCKET_ERROR == setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)&buf_size, sizeof(buf_size)))
            err = LTRSRV_ERR_SOCKET_SET_BUF_SIZE;
    }
#endif
#ifdef LTRD_ETH_CRATE_SOCK_RECV_BUF_SIZE
    if (!err) {
        uint32_t buf_size = LTRD_ETH_CRATE_SOCK_RECV_BUF_SIZE;
        if (L_SOCKET_ERROR == setsockopt(s, SOL_SOCKET , SO_RCVBUF, (char*)&buf_size, sizeof(buf_size)))
            err = LTRSRV_ERR_SOCKET_SET_BUF_SIZE;
    }
#endif

    if (!err && f_eth_nodelay) {
        err = f_sock_set_nodelay(s, 1);
    }

    if (!err) {
        /* заполняем структуру с адресом LTR-сервера */
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = family;
        peer.sin_port = htons(port);
        if (inet_pton(family, ip_addr, &peer.sin_addr.s_addr)!=1)
            err = LTRSRV_ERR_SOCKET_INET_ADDR;
    }

    if (!err) {
        if (L_SOCKET_ERROR == connect(s, (struct sockaddr*)&peer, sizeof(peer))) {
#ifdef _WIN32
            if (WSAEWOULDBLOCK != WSAGetLastError()) {
#else
            if (errno != EINPROGRESS) {
#endif            
                err = LTRSRV_ERR_SOCKET_CONNECT;
            } else {
                *wt = 1;
                /* соединение не может быть выполнено сейчас */
                err = ltrsrv_wait_add_sock(s, LTRSRV_WAITFLG_SEND | LTRSRV_WAITFLG_TOUT | LTRSRV_WAITFLG_AUTODEL,
                                           f_eth_con_timeout, cb, data);
            }
        } else {
            *wt = 0;
        }
    }

    if (err && (s!=L_INVALID_SOCKET)) {
        closesocket(s);
        s = L_INVALID_SOCKET;
    }

    if (psock)
        *psock = s;

    return err;
}

static void f_cmd_proc_finish(t_ltr_crate *crate, int err) {
    t_eth_data *intf = (t_eth_data *)crate->intf_data;
    intf->cmd_st = ETH_CMD_STATE_IDLE;
    if (intf->proc_cmd_cb)
        intf->proc_cmd_cb(crate, err, intf->proc_data);

    /* если соединения больше не существует, то нужно его закрыть в любом случае */
    if ((err == LTRSRV_ERR_SOCKET_CON_RESET) || (err == LTRSRV_ERR_SOCKET_ABORTED)
            || (err == LTRSRV_ERR_SOCKET_CLOSED)) {
        ltrsrv_crate_close_req(crate, err);
    }
}


/* Функция для передачи команды. Пытается передать всю команду, если удалось -
   переходит к приему, иначе - обновляет количество переданных слов и ожидает
   освобождения сокета на запись */
static int f_ctl_proc_send(t_ltr_crate *crate) {
    int err=0, send_size, sended;
    t_eth_data *intf = (t_eth_data *)crate->intf_data;

    send_size = intf->req->VarDataLen + ETH_CTL_REQ_HDR_SIZE - intf->cur_pos;
    /* появилось место в сокете для передачи данных => пробуем передать остаток буфера */
    sended = send(intf->cmd_sock, &((uint8_t*)intf->req)[intf->cur_pos],
                               send_size, 0);

    if (sended == send_size) {
        /* если все послали - то переходим к приему */
        intf->cmd_st = ETH_CMD_STATE_WAIT_RECV_BUF;
        intf->cur_pos = 0;
        err = ltrsrv_wait_add_sock(intf->cmd_sock, LTRSRV_WAITFLG_RECV | LTRSRV_WAITFLG_TOUT | LTRSRV_WAITFLG_AUTODEL,
                             LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&intf->cmd_tmr)),
                             f_ctl_cb, crate);
    } else if ((sended >= 0) || ((sended == L_SOCKET_ERROR) && L_SOCK_LAST_ERR_BLOCK())) {
        /* если передали не все данные из-за недостатка места -
           ждем, пока данные не освободятся */
        intf->cmd_st = ETH_CMD_STATE_WAIT_SEND_BUF;
        intf->cur_pos += sended > 0 ? sended : 0;
        err = ltrsrv_wait_add_sock(intf->cmd_sock, LTRSRV_WAITFLG_SEND | LTRSRV_WAITFLG_TOUT | LTRSRV_WAITFLG_AUTODEL,
                             LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&intf->cmd_tmr)),
                             f_ctl_cb, crate);
    } else {
        err = LTRSRV_ERR_SOCKET_SEND;
        intf->cmd_st = ETH_CMD_STATE_IDLE;
    }
    return err;
}

/****************************************************************************//**
    Запуск передачи управляющего запроса по управляющему сокету
    @param[in] crate       Указатель на описатель крейта (подразуемеваем, что верный)
    @param[in] cmd         Код команды
    @param[in] param       Параметр команды
    @param[in] reply_len  Ожидаемый размер данных в ответе в байтах
    @param[in] data_len    Размер данных на передачу
    @param[in] data        Данные на передачу (имеет значение только если data_len/=0
    @return                Код ошибки
   ******************************************************************************/
static int f_start_ctl_req(t_ltr_crate* crate, uint32_t cmd, uint32_t param, uint32_t reply_len,
                           uint32_t data_len, const uint8_t *data) {
    t_eth_data *intf = NULL;
    unsigned int send_size = data_len + ETH_CTL_REQ_HDR_SIZE;
    unsigned int recv_size = reply_len + ETH_CTL_REPLY_HDR_SIZE;
    int err = _ETH_CHECK_HND(crate);

    if (!err) {
        intf = (t_eth_data*)crate->intf_data;

        /* запускаем таймер выполнения команды */
        ltimer_set(&intf->cmd_tmr, LTIMER_MS_TO_CLOCK_TICKS(f_eth_cmd_timeout));
        ltimer_restart(&crate->intf_func.poll_timer);
    }


    /* если сейчас выделено памяти меньше, чем требуется для запроса - выделяем
      больше */
    if (!err && (send_size > intf->req_size)) {
        if (intf->req)
            free(intf->req);

        intf->req = (t_eth_ctl_req*)malloc(send_size);
        if (intf->req==NULL) {
            err = LTRSRV_ERR_MEMALLOC;
            intf->req_size = 0;
        } else {
            intf->req_size = send_size;
        }
    }

    /* выделяем буфер для приема */
    if (!err && (recv_size > intf->reply_size)) {
        if (intf->reply)
            free(intf->reply);

        intf->reply = (t_eth_ctl_reply *)malloc(recv_size);
        if (intf->reply == NULL) {
            err = LTRSRV_ERR_MEMALLOC;
            intf->reply_size = 0;
        } else {
            intf->reply_size = recv_size;
        }
    }

    if (!err) {
        /* заполняем параметры запроса и пробуем послать его модулю */
        intf->req->Signature = LTR_ETH_CTRL_SIG;
        intf->req->CmdCode = cmd;
        intf->req->CmdParam = param;
        intf->req->ExpectReplyLen = reply_len;
        intf->req->VarDataLen = data_len;
        if (data_len)
            memcpy(intf->req->data, data, data_len);
        intf->cur_pos = 0;
        intf->cmd_st = ETH_CMD_STATE_WAIT_SEND_BUF;

        /* запускаем передачу команды */
        err = f_ctl_proc_send(crate);

    }
    return err;
}


static t_ltr_crate_ip_entry *f_get_crate_ipentry(t_ltr_crate *crate) {
    int i;
    t_ltr_crate_ip_entry *ret = NULL;
    for (i=0; (i < f_ip_cnt) && (ret==NULL); i++) {
        if (f_ip_entries[i].crate == crate) {
            ret = &f_ip_entries[i];
        }
    }
    return ret;
}


static int f_start_get_name(t_ltr_crate *crate) {
    return f_start_ctl_req(crate, LTR_ETH_CMD_GET_NAME, 0,
                            LTR_CRATE_REG_NAME_SIZE,0, NULL);
}

static int f_start_get_descr(t_ltr_crate *crate) {
    return f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                      LTR_CRATE_ADDR_MODULE_DESCR,
                      sizeof(t_ltr_device_raw_descr), 0, NULL);
}

static int f_start_get_primary_con_status(t_ltr_crate *crate) {
    return f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                        LTR_CRATE_ADDR_CONNECTION_PRIMARY,
                        LTR_CRATE_REG_CONNECTION_PRIMARY_SIZE,
                        0, NULL);
}


static void f_print_connection_err(t_ltr_crate *crate, int err, const char *str) {
    t_ltr_crate_ip_entry *ip_entry = f_get_crate_ipentry(crate);
    int recon = ip_entry && (ip_entry->recon_cnt != 0);
    if (recon) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, err, "Reconnection faild (reconnection number %d): %s", ip_entry->recon_cnt, str);
    } else {
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, err, "%s", str);
    }
}




static int f_check_sec_con_data_close(struct st_ltr_crate *crate, int err, void *prv_data) {
    (void)prv_data;

    /* Если вместе с сокетом данных крейт закрыл корректно и управляющее соединение, то
     * считаем, что это соответстует закрытию соединения из-за установки соединения
     * другим клиентом. */
    /** @todo возможно стоит попробовать заново утсановить контрольное соединение
     * только для проверки наличия второго */
    /** @todo посылка специального кода исключения в новых версиях прошивок? */
    if (err == LTRSRV_ERR_SOCKET_CLOSED) {
        err = 0;
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, err,
                       "Crate close data and control sockets. Another host can establish primary connection...");
        ltrsrv_crate_close_req(crate, 0);
    } else if (err == LTRSRV_ERR_SUCCESS) {
        t_eth_data *intf = (t_eth_data *)crate->intf_data;
        int is_primary =0;

        intf->flags &= ~ETH_FLAGS_DATA_SOCK_CHECK_SEC_CON;

        if (intf->work_cmd_ctx.recvd_size > 0) {
            is_primary = intf->work_cmd_ctx.recvd_data[0];
        }

        if (!is_primary) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0,
                           "Crate change connection status to secondary. Another host established crate primary connection.");
        } else {
            err = f_start_data_recon(crate);
        }
    }

    if (err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, err,
                       "Crate close data socket. Reconnection attemt error");
        ltrsrv_crate_close_req(crate, err);
    }
    return  err;
}


/****************************************************************************//**
  Функция вызывается после того, как команда была передана и получен весь ответ.
    Переданная команда находится в st->req, а ответ в st->reply.
    Сигнатура ответа и что размер не превышает запрошенный уже проверено при
    приеме.
    @param[in] crate  указатель на описатель крейта (подразуемеваем, что верный)
    @return           код ошибки
 ******************************************************************************/
static int f_process_cmd(t_ltr_crate* crate) {
    int err = 0;
    t_eth_data *intf = (t_eth_data*)crate->intf_data;

    if (intf->reply->ResultCode!=0) {
        err = LTRSRV_ERR_ETH_REPLY_RESULT;
        intf->cmd_st = ETH_CMD_STATE_ERROR;
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "Receive control command response with result = %d", intf->reply->ResultCode);
    } else if (crate->par.state == LTR_CRATE_CONSTATE_INITIALIZATION) {
        /* на стадии инициализации разбираем команды на уровне интерфейса */
        switch (intf->req->CmdCode) {
            case LTR_ETH_CMD_INIT:
                if (intf->reply->VarDataLen < (LTR_CRATE_FPGA_INFO_SIZE_MIN*2)) {
                    err = LTRSRV_ERR_CRATE_FPGA_INFO_SIZE;
                } else {
                    unsigned int i;
                    uint16_t* raw_data = (uint16_t*)intf->reply->data;

                    /* проверяем правильность ответа. должно быть
                       хотя бы 250 двухбайтовых слов, хотя в EU возвращается 255.
                       В данном случае мы все их анализируем
                       При этом старший байт всегда 0xE0, а младший - собственно данные.
                        Очищенные от 0xE0 данные сохраняем туда же */
                    for (i = 0; (i < intf->reply->VarDataLen/2) &&
                         (i<LTR_CRATE_FPGA_INFO_SIZE_MIN) && !err; i++) {
                        if ((raw_data[i] >> 8) != 0xE0) {
                            err = LTRSRV_ERR_CRATE_FPGA_INFO_FORMAT;
                        }
                        intf->reply->data[i] = raw_data[i] & 0xFF;
                    }

                    /* если крейт поддерживает новый протокол, то в данных,
                     * начиная с 504 байта должна содержаться сигнатура 4 байта,
                     * а затем номер версии */
                    if ((intf->reply->VarDataLen >= (LTR_PROTOCOL_VERSION_MAGIC_POS+6))
                            && (intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS]
                                ==(LTR_PROTOCOL_VERSION_MAGIC & 0xFF))
                            && (intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS+1]
                                ==((LTR_PROTOCOL_VERSION_MAGIC>>8) & 0xFF))
                            && (intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS+2]
                                ==((LTR_PROTOCOL_VERSION_MAGIC>>16) & 0xFF))
                            && (intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS+3]
                                ==((LTR_PROTOCOL_VERSION_MAGIC>>24) & 0xFF))) {

                        crate->descr.protocol_ver_major = intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS+4];
                        crate->descr.protocol_ver_minor = intf->reply->data[LTR_PROTOCOL_VERSION_MAGIC_POS+5];
                    } else {
                        crate->descr.protocol_ver_major = 0;
                        crate->descr.protocol_ver_minor = 0;
                    }
                }

                /* Полученные данные находятся в формате <key>{value}
                   Разбираем их по полям структуры */
                if (!err) {
                    err = ltrsrv_crate_init_parse_fpga_data(
                                (const char*)intf->reply->data, &crate->descr);
                }

                if (!err) {
                    /* при наличии версии протокола - проверяем,
                     * первичное ли это подключение */
                    if (LTR_CRATE_SUPPORT_SEC_CTL_CON(crate)) {
                        err = f_start_get_primary_con_status(crate);
                    } else {
                        /* иначе переходим к получению имени крейта */
                        err = f_start_get_name(crate);
                    }
                }
                break;
            case LTR_ETH_CMD_GET_NAME:
                memcpy(crate->descr.devname, intf->reply->data, intf->reply->VarDataLen);
                ltrsrv_crate_init_spec(crate);
                if (crate->type == LTR_CRATE_TYPE_LTR032) {
                    err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                                       LTR_CRATE_ADDR_FPGA_STATE,
                                       LTR_CRATE_REG_FPGA_STATE_SIZE, 0, NULL);
                } else {
                    err = f_start_get_descr(crate);
                }
                break;
            case LTR_ETH_CMD_GET_ARRAY:
            case LTR_ETH_CMD_PUT_ARRAY:
                err = ltrsrv_crate_init_process_data(crate, intf->req->CmdParam, intf->reply->data, intf->reply->VarDataLen);
                if (!err) {
                    switch (intf->req->CmdParam) {
                        case LTR_CRATE_ADDR_CONNECTION_PRIMARY: {
                                uint8_t is_primary = intf->reply->data[0];
                                if (!is_primary) {
                                    ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0,
                                                   "Connection already established! Force primary connection!");
                                }
                                /* Всегда делаем сброс соединения данных, даже если текущее
                                 * соединение - primary. Это ничему не вредит, но
                                 * может помочь с глюком LTR-CEU-1 с прошивками до 1.0.6,
                                 * т.к. в нем могло оставаться старое возможно некоректно
                                 * оборванное соединение по primary-подключению */
                                err = f_start_ctl_req(crate, LTR_ETH_CMD_PUT_ARRAY,
                                                    LTR_CRATE_ADDR_CONNECTION_FORCE,
                                                    0, 0, NULL);
                            }
                            break;
                        case LTR_CRATE_ADDR_CONNECTION_FORCE:
                            /* если есть расширенная версия протокола, то считаем, что
                             * должны поддерживать возможность получить версию
                             * протокола для сохранения настроек модуля */
                            err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                                               LTR_CRATE_ADDR_SLOT_CONFIG_VER,
                                               LTR_CRATE_REG_SLOT_CONFIG_VER_SIZE,
                                               0, NULL);
                            break;
                        case LTR_CRATE_ADDR_SLOT_CONFIG_VER:
                            err = f_start_get_name(crate);
                            break;
                        case LTR_CRATE_ADDR_FPGA_STATE:
                            err = f_start_get_descr(crate);
                            break;
                        case LTR_CRATE_ADDR_MODULE_DESCR:
                            err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                                                      LTR_CRATE_ADDR_BOOTLOADER_VER,
                                                      LTR_CRATE_REG_BOOTVER_VER_SIZE,
                                                      0, NULL);
                            break;
                        case LTR_CRATE_ADDR_BOOTLOADER_VER:
                             err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                                                   LTR_CRATE_ADDR_FIRMWARE_VER,
                                                   LTR_CRATE_REG_FIRMWARE_VER_SIZE,
                                                   0, NULL);
                            break;
                        case LTR_CRATE_ADDR_FIRMWARE_VER:
                            if (crate->descr_flags & LTR_CRATE_DESCR_FLAGS_SUPPORT_BOARD_REV) {
                                err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY,
                                                   LTR_CRATE_ADDR_BOARD_REV,
                                                   LTR_CRATE_REG_BOARD_REV_SIZE,
                                                   0, NULL);
                                break;
                            }
                            /* fallthrough */
                        case LTR_CRATE_ADDR_BOARD_REV:
                            intf->state = ETH_INIT_STATE_DATA_CH_CONN;
                            break;
                    }
                }
                break;
            default:
                err = LTRSRV_ERR_ETH_INVALID_CMD;
                break;
        }
    } else if (crate->par.state == LTR_CRATE_CONSTATE_WORK) {
        /* в рабочем состоянии все команды выполняются от лица модуля управления крейтами
           и мы просто проверяем размер и вызываем оставленный нам callback */

        if ((intf->reply->VarDataLen !=  intf->req->ExpectReplyLen) &&
                (intf->proc_rx_size==NULL)) {
            err = LTRSRV_ERR_CRATE_CMD_REPLY_LEN;
        } else {
            /* если c ответом есть данные => копируем в пользовательский буфер */
            if (intf->proc_buf && intf->reply->VarDataLen) {
                memcpy(intf->proc_buf, intf->reply->data, intf->reply->VarDataLen);
            }

            if (intf->proc_rx_size!=NULL) {
                *intf->proc_rx_size = intf->reply->VarDataLen;
            }
            intf->proc_rx_size = NULL;

            f_cmd_proc_finish(crate, 0);
        }
    }
    return err;
}


/** Обработка события сокета при обработке управляющей команды
    @param[in] crate  Описатель крейта
    @param[in] event  Флаги события из t_ltrsrv_wait_event */
static int f_ctl_cmd_event(t_ltr_crate* crate, int event) {
    int err = 0;
    t_eth_data *intf = (t_eth_data *)crate->intf_data;


    if ((intf->cmd_st == ETH_CMD_STATE_WAIT_SEND_BUF) && (event == LTRSRV_EVENT_SEND_RDY)) {
        err = f_ctl_proc_send(crate);
    } else if ((intf->cmd_st == ETH_CMD_STATE_WAIT_RECV_BUF) && (event == LTRSRV_EVENT_RCV_RDY)) {
        int received;

        /* определяем, сколько надо принять еще данных - если заголовок ответа еще не
           приняли, то пытаемся принять сколько запросили, иначе - сколько реально
           указано в заголовке */
        int rcv_size = intf->cur_pos < ETH_CTL_REPLY_HDR_SIZE ?
                           ETH_CTL_REPLY_HDR_SIZE + intf->req->ExpectReplyLen :
                           ETH_CTL_REPLY_HDR_SIZE + intf->reply->VarDataLen;
        rcv_size -= intf->cur_pos;

        /* принимаем часть ответа */
        err = ltrsrv_sock_recv(intf->cmd_sock, &((uint8_t*)intf->reply)[intf->cur_pos],
                               rcv_size, &received);

        if (!err) {
            unsigned int new_pos = intf->cur_pos + received;

            /* если при приеме этого куска данных мы приняли сигнатуру
               ответа, то проверяем ее на правильность */
            if ((intf->cur_pos < sizeof(intf->reply->Signature)) &&
                    (new_pos >= sizeof(intf->reply->Signature))) {
                if (intf->reply->Signature != LTR_ETH_CTRL_SIG) {
                    err = LTRSRV_ERR_ETH_REPLY_SIGNATURE;
                }
            }

            /* если с этим куском данных мы приняли заголовок ответа, то
               пересчитываем размер в соответствии с размером из ответа и
               проверяем, что ответ на превышает запрашиваемый размер */
            if (!err && (intf->cur_pos < ETH_CTL_REPLY_HDR_SIZE) &&
                    (new_pos >= ETH_CTL_REPLY_HDR_SIZE)) {
                if (intf->reply->VarDataLen > intf->req->ExpectReplyLen) {
                    err = LTRSRV_ERR_ETH_REPLY_SIZE;
                } else {
                    rcv_size = ETH_CTL_REPLY_HDR_SIZE + intf->reply->VarDataLen - intf->cur_pos;
                }
            }


            if (!err) {
                /* если еще не все приняли - то ждем дальше данных */
                if (received < rcv_size) {
                    intf->cur_pos += received;
                    err = ltrsrv_wait_add_sock(intf->cmd_sock,
                                               LTRSRV_WAITFLG_RECV | LTRSRV_WAITFLG_TOUT | LTRSRV_WAITFLG_AUTODEL,
                                               LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&intf->cmd_tmr)),
                                               f_ctl_cb, crate);
                } else {
                    /* приняли полностью ответ от команды !! */
                    intf->cur_pos = 0;
                    intf->cmd_st = ETH_CMD_STATE_IDLE;
                    ltrsrv_wait_add_sock(intf->cmd_sock, LTRSRV_WAITFLG_RECV, 0, f_ctl_cb, crate);

                    err = f_process_cmd(crate);
                }
            }
        }
    } else if (event == LTRSRV_EVENT_TOUT){
        /* вышли по таймауту */
        err = intf->cmd_st == ETH_CMD_STATE_WAIT_RECV_BUF ? LTRSRV_ERR_ETH_CMD_RECV_TOUT :
                                                         LTRSRV_ERR_ETH_CMD_SEND_TOUT;
    } else {
        err = LTRSRV_ERR_ETH_CMD_EVENT;
    }

    if (err) {
        ltrsrv_log_rec(intf->proc_cmd_cb==NULL ? LTRSRV_LOGLVL_ERR : LTRSRV_LOGLVL_DETAIL, crate->log_str, err,
                       "Control command %s (param=0x%x) failed",
                       LOGSTR_CMD_CODE(intf->req->CmdCode), intf->req->CmdParam);
    }

    return err;
}



/** Функция, вызываемая при завершении инициализации (которая заканчивается после
    установки соеденинеия по каналу данных. Регистрирует крейт в системе и
    начинает прослушку сокета с данными */
static int f_data_con_done(t_ltr_crate *crate) {
    int err = 0;
    t_eth_data *intf = (t_eth_data *)crate->intf_data;

    intf->state = ETH_INIT_STATE_DONE;
    ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, crate->log_str, 0, "Data socket connection done");
    /* регистрируем прошедший все этапы инициализации крейт */    
    err = ltrsrv_crate_register(crate);
    if (!err) {
        t_ltr_crate_ip_entry *ip_entry = f_get_crate_ipentry(crate);
        if (ip_entry) {
            ip_entry->status = LTR_CRATE_IP_STATUS_ONLINE;
            /* сбрасываем кол-во переповторов при удачном соединении */
            ip_entry->recon_cnt = 0;
        }
        /* добавляем сокет с данными на ожидание */
        err = ltrsrv_wait_add_sock(intf->data_sock, LTRSRV_WAITFLG_RECV, 0, f_data_cb, crate);
    }
    return err;
}

#ifdef LTRD_ETH_DUMP_ENABLED
static void f_save_dump(t_ltr_crate *crate) {
    FILE *f;
    time_t cur_time = time(NULL);
    struct tm *tm = localtime(&cur_time);
    static char filename[512];
    t_eth_data *intf = (t_eth_data *)crate->intf_data;

    sprintf(filename, "c:\\%s_%04d_%02d_%02d__%02d_%02d_%02d.txt",
            crate->descr.serial,
            tm->tm_year+1900,  tm->tm_mon, tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec);

    f = fopen(filename, "w");
    if (f!=NULL) {
        unsigned pos,p;
        for (pos=intf->dump_pos, p=0; p < DUMP_PACKET_CNT; p++) {
            t_dump_packet *pack = &intf->dump[pos];
            if (pack->time) {
                unsigned i;
                tm = localtime(&pack->time);
                fprintf(f, "%4d [%0d.%02d.%04d %d:%02d:%02d]: ", p, tm->tm_mday, tm->tm_mon, tm->tm_year+1900,
                                                       tm->tm_hour, tm->tm_min, tm->tm_sec);
                if (pack->dir) {
                    fprintf(f, "send  %5d (req %5d)\n", pack->size, pack->snd_req);
                } else {
                    fprintf(f, "recvd %5d\n", pack->size);
                }

                for (i=0; i < pack->size; i+=4) {
                    fprintf(f, "       %02X%02X%02X%02X\n", pack->data[i+3], pack->data[i+2], pack->data[i+1], pack->data[i]);
                }
            }
            DUMP_NEXT_POS(pos);
        }
        fclose(f);
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "save dump: %s",
                       filename);
    } else {
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, LTRSRV_ERR_SAVECFG_FILE, "save dump (%d) error",
                       filename);
    }
}
#endif


static int f_start_data_recon(t_ltr_crate *crate) {
    int err = 0;
    int wt = 0;
    t_eth_data *intf = (t_eth_data *)crate->intf_data;
    if (++intf->data_recon_cnt <= LTR_TCP_DATA_RECON_LIMIT) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0,
                       "Data socket closed by crate. Start data socket reconnection (reconnection number = %d)!",
                       intf->data_recon_cnt);
        err = f_sock_start_conn(&intf->data_sock, AF_INET, crate->par.location,
                              LTR_TCP_DATA_PORT, &wt, crate, f_data_cb);
        if (!err) {
            if (wt) {
                intf->flags |= ETH_FLAGS_DATA_SOCK_RECONNECTION;
            } else {
                f_data_reconnection_finish(crate);
            }
        }
    } else {
        err = LTRSRV_ERR_CRATE_DATA_RECON_EXCEEDED;
    }
    return  err;
}

static void f_data_reconnection_finish(t_ltr_crate *crate) {
    t_eth_data *intf = (t_eth_data *)crate->intf_data;
    int err;

    intf->flags &= ~ETH_FLAGS_DATA_SOCK_RECONNECTION;
    err = ltrsrv_wait_add_sock(intf->data_sock, LTRSRV_WAITFLG_RECV, 0, f_data_cb, crate);
    if (!err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, err, "Data socket reconnection done");
        ltrsrv_crate_data_channel_reset(crate);
    }
}

/***************************************************************************//**
    Callback-функция, вызываемая, если произошло событие по сокету для передачи
        данных
    @param[in] sock      Сокет, по которому произошло событие
    @param[in] event     Набор флагов, определяющий, какие события произошли
    @param[in] data      Указатель на описатель крейта
    ***************************************************************************/
static void f_data_cb(t_socket sock, int event, void* data) {

    t_ltr_crate *crate = (t_ltr_crate*)data;
    t_eth_data *intf;
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        intf = (t_eth_data *)crate->intf_data;
    }

    if (!err){
        /* в первую очередь проверяем, что не было исключительной ситуации по
           сокету */
        if (event & LTRSRV_EVENT_EXCEPTION)
            err = LTRSRV_ERR_SOCKET_ABORTED;
    }

    /* если пришли данные - пробуем принять их в свой мини-буфер */
    if (!err && (event & LTRSRV_EVENT_RCV_RDY)) {
        int received;

        err = ltrsrv_sock_recv(sock, &f_rcv_buf[intf->rcv_data_offs],
                LTRSRV_ETH_RCV_BUF_SIZE - intf->rcv_data_offs, &received);
        if (err == LTRSRV_ERR_SOCKET_CLOSED) {
            /* сокет был закрыт другой стороной - закрываем его сами и пробуем
               установить соединение заново. Однако ограничиваем число
               попыток, чтобы бесконечно не переоткрывать */

            intf->rcv_data_offs = 0;
            ltrsrv_wait_remove_sock(intf->data_sock);
            closesocket(intf->data_sock);
            intf->data_sock = L_INVALID_SOCKET;


            if (LTR_CRATE_SUPPORT_SEC_CTL_CON(crate)) {
                if (!(intf->flags & ETH_FLAGS_DATA_SOCK_CHECK_SEC_CON)) {
                    intf->flags |= ETH_FLAGS_DATA_SOCK_CHECK_SEC_CON;
                    err = ltrsrv_crate_add_cmd_to_queue(crate,
                                                        LTR_CRATE_CMD_CODE_READ,
                                                        LTR_CRATE_CMD_REASON_INTF_REQ,
                                                        0,
                                                        LTR_CRATE_ADDR_CONNECTION_PRIMARY,
                                                        0,
                                                        LTR_CRATE_REG_CONNECTION_PRIMARY_SIZE,
                                                        &intf->work_cmd_ctx.recvd_size,
                                                        intf->work_cmd_ctx.recvd_data,
                                                        LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF,
                                                        f_check_sec_con_data_close, NULL);
                }
            } else {
                err = f_start_data_recon(crate);
            }
        } else if (!err) {
#ifdef LTRD_ETH_DUMP_ENABLED
            uint32_t save_size = received;
            intf->dump[intf->dump_pos].dir = 0;
            intf->dump[intf->dump_pos].size = received;
            intf->dump[intf->dump_pos].time = time(NULL);
            if (save_size < DUMP_PACKET_MAX_SIZE)
                save_size = DUMP_PACKET_MAX_SIZE;
            memcpy(intf->dump[intf->dump_pos].data, &f_rcv_buf[intf->rcv_data_offs],
                    save_size);
            DUMP_NEXT_POS(intf->dump_pos);
#endif
            /* если был хвост - копируем его в начало буфера */
            if (intf->rcv_data_offs)
                memcpy(f_rcv_buf, &intf->rcv_last_wrd, intf->rcv_data_offs);
            received+= intf->rcv_data_offs;
            /* если появился новый хвост - сохраняем его */
            intf->rcv_data_offs = received % sizeof(uint32_t);
            if (intf->rcv_data_offs) {
                memcpy(&intf->rcv_last_wrd, &f_rcv_buf[received-intf->rcv_data_offs],
                       intf->rcv_data_offs);
            }

            /* если пришли данные - передаем их модулю обработки */
            err = ltrsrv_crate_rcv_done(crate, (uint32_t*)f_rcv_buf, received/sizeof(uint32_t));
        }
    }

    /* готовность на передачу приходит либо если мы делали реконнект сокета, либо
       если нас попросил верхний уровень (в этом случае вызываем его callback) */
    if (!err && (event & LTRSRV_EVENT_SEND_RDY)) {
        if (intf->flags & ETH_FLAGS_DATA_SOCK_RECONNECTION) {
            err = ltrsrv_sock_con_done_check(sock);
            if (err) {
                f_print_connection_err(crate, err, "Data socket connection failed");
            } else {
                f_data_reconnection_finish(crate);
            }
        }

        if (!err) {
            /* ожидаем снова только на прием */
            err = ltrsrv_wait_add_sock(sock, LTRSRV_WAITFLG_RECV, 0, f_data_cb, crate);
            if (!err && (intf->snd_rdy_cb!=NULL)) {
                t_ltr_crate_snd_rdy_cb cb = intf->snd_rdy_cb;
                intf->snd_rdy_cb = NULL;
                err = cb(crate);
            }
        }
    }

    if (err) {
        f_print_connection_err(crate, err, "data socket error");
        f_intf_close(crate, err);
    } else {
        ltrsrv_crate_check_close_req(crate);
    }
}

/** Callback функция на события по управляющему сокету, в ней разбирается процесс
    установления связи по сокетам, а так же процесс передачи команд инициализации */
static void f_ctl_cb(t_socket sock, int event, void* data) {
    t_ltr_crate *crate = (t_ltr_crate*)data;
    t_eth_data *intf=NULL;
    int err = _ETH_CHECK_HND(crate);

    if (!err && (event & LTRSRV_EVENT_SRV_CLOSE)) {
        f_intf_close(crate, 0);
    } else {
        if (!err) {
            intf = (t_eth_data *)crate->intf_data;

            /* в первую очередь проверяем, что не было исключительной ситуации по
               сокету */
            if (event & LTRSRV_EVENT_EXCEPTION) {
                err = LTRSRV_ERR_SOCKET_ABORTED;
                f_print_connection_err(crate, err, "");
            }
        }

        if (!err) {
            /* если выполняется команда - то обрабатываем ее */
            if (intf->cmd_st != ETH_CMD_STATE_IDLE) {
                err = f_ctl_cmd_event(crate, event);

                /* как закончили передовать последовательность команд - переходим
                   к открытию канала данных */
                if (!err && (intf->state==ETH_INIT_STATE_DATA_CH_CONN)) {
                    int wt;
                    ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, crate->log_str, 0, "Data socket connection started");
                    err = f_sock_start_conn(&intf->data_sock, AF_INET, crate->par.location,
                                          LTR_TCP_DATA_PORT, &wt, crate, f_ctl_cb);
                    if (!err && !wt) {
                        err = f_data_con_done(crate);
                    }
                }

            } else {
                switch (intf->state) {
                    case ETH_INIT_STATE_CTL_CH_CONN:
                        if (event == LTRSRV_EVENT_TOUT) {
                            err = LTRSRV_ERR_ETH_CONNECTION_TOUT;
                        } else {
                            err = ltrsrv_sock_con_done_check(sock);
                        }

                        if (!err && (event & LTRSRV_EVENT_SEND_RDY)) {
                            intf->state = ETH_INIT_STATE_INITIALIZATION;
                            err = f_start_ctl_req(crate, LTR_ETH_CMD_INIT, 0,
                                                  LTR_CRATE_FPGA_INFO_SIZE_MAX*2, 0, NULL);
                            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, crate->log_str, 0, "Control socket connection done");
                        }

                        break;
                   case ETH_INIT_STATE_DATA_CH_CONN:
                        if (event == LTRSRV_EVENT_TOUT) {
                            err = LTRSRV_ERR_ETH_CONNECTION_TOUT;
                        } else {
                            err = ltrsrv_sock_con_done_check(sock);
                        }

                        if (!err && (event & LTRSRV_EVENT_SEND_RDY)) {
                            err = f_data_con_done(crate);
                        }
                        break;
                    default:
                        err = ltrsrv_sock_con_done_check(sock);
                        if (!err && (event == LTRSRV_EVENT_RCV_RDY)) {
                            uint8_t tmp;
                            int received;
                            err = ltrsrv_sock_recv(intf->cmd_sock, &tmp, sizeof(tmp), &received);
                            if (!err)
                                err = LTRSRV_ERR_CRATE_UNEXPECTED_CTL_DATA;
                        }
                        break;
                }
            }
        }

        if (err) {

            /* если прошили стадию инициализации - то это команда от верхнего
               уровня => если произошла ошибка - вызываем callback, чтобы оповестить */
            if ((crate->par.state == LTR_CRATE_CONSTATE_WORK) && (intf->cmd_st != ETH_CMD_STATE_IDLE)) {
                f_cmd_proc_finish(crate, err);
            } else {
                switch (intf->state) {
                    case ETH_INIT_STATE_CTL_CH_CONN:
                        f_print_connection_err(crate, err, "Control socket connection failed");
                        break;
                    case ETH_INIT_STATE_DATA_CH_CONN:
                        f_print_connection_err(crate, err, "Data socket connection failed");
                        break;
                    case ETH_INIT_STATE_INITIALIZATION:
                        /** @todo более подробное сообщение, на какой стадии была ошибка */
                        f_print_connection_err(crate, err, "Control error on init stage");
                        break;
                    default:
                        if (err == LTRSRV_ERR_SOCKET_CLOSED) {
                            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str,0,
                                           "Crate close control connection");
                        } else {
                            f_print_connection_err(crate, err, "Control connection error");
                        }
                        break;
                }

                ltrsrv_crate_close_req(crate, err);
            }
        }


        ltrsrv_crate_check_close_req(crate);
    }
}

/* Интерфейсня функция типа t_ltr_crate_regs_read.
   Чтение регистров из крейта.
   Функция запускает процедуру передачи команды GET_ARRAY и по завершению
   чтения вызывает указанную callback-функцию */
static int f_intf_regs_read(t_ltr_crate* crate, uint32_t addr, int size,
                          uint8_t* buf, t_ltr_crate_cmd_cb cb, void *data) {
    t_eth_data *intf;
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        intf = (t_eth_data *)crate->intf_data;

        /* проверяем, что последовательность инициализации уже завершена */
        if (intf->state != ETH_INIT_STATE_DONE) {
            err = LTRSRV_ERR_CRATE_CRATE_NOT_INIT;
        /*  проверяем, что сейчас команда ни одна не выполняется */
        } else if (intf->cmd_st != ETH_CMD_STATE_IDLE) {
            err = LTRSRV_ERR_CRATE_CMD_IN_PROGRESS;
        }
    }

    if (!err) {
        err = f_start_ctl_req(crate, LTR_ETH_CMD_GET_ARRAY, addr, size, 0, NULL);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = buf;
            intf->proc_rx_size = NULL;

            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_LOW, crate->log_str, 0,
                           "Crate registers read: addr 0x%08X, size %d",
                           addr, size);
        }
    }



    return err;
}

static int f_intf_regs_write(t_ltr_crate* crate, uint32_t addr, int size,
                          const uint8_t* buf, t_ltr_crate_cmd_cb cb, void* data) {
    t_eth_data *intf;
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        intf = (t_eth_data *)crate->intf_data;

        /* проверяем, что последовательность инициализации уже завершена */
        if (intf->state != ETH_INIT_STATE_DONE) {
            err = LTRSRV_ERR_CRATE_CRATE_NOT_INIT;
        /*  проверяем, что сейчас команда ни одна не выполняется */
        } else if (intf->cmd_st != ETH_CMD_STATE_IDLE) {
            err = LTRSRV_ERR_CRATE_CMD_IN_PROGRESS;
        }
    }

    if (!err) {
        err = f_start_ctl_req(crate, LTR_ETH_CMD_PUT_ARRAY, addr, 0, size, buf);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = NULL;
            intf->proc_rx_size = NULL;

            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, 0,
                           "Crate registers write: addr 0x%08X, size %d",
                           addr, size);
        }
    }



    return err;
}


static int f_intf_ioctl(t_ltr_crate* crate, uint32_t req, uint32_t param,
                        const uint8_t* snd_buf, uint32_t snd_size,
                        uint8_t* rcv_buf, uint32_t* rcv_size,
                        t_ltr_crate_cmd_cb cb, void* data) {
    t_eth_data *intf;
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        intf = (t_eth_data *)crate->intf_data;

        /* проверяем, что последовательность инициализации уже завершена */
        if (intf->state != ETH_INIT_STATE_DONE) {
            err = LTRSRV_ERR_CRATE_CRATE_NOT_INIT;
        /*  проверяем, что сейчас команда ни одна не выполняется */
        } else if (intf->cmd_st != ETH_CMD_STATE_IDLE) {
            err = LTRSRV_ERR_CRATE_CMD_IN_PROGRESS;
        }
    }

    if (!err) {        
        err = f_start_ctl_req(crate, req, param, *rcv_size, snd_size, snd_buf);
        if (!err) {
            intf->proc_cmd_cb = cb;
            intf->proc_data = data;
            intf->proc_buf = rcv_buf;
            intf->proc_rx_size = rcv_size;


            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_LOW, crate->log_str, 0,
                           "Start ioctl: req = 0x%08X, param = 0x%08X, rcv_size = %d, snd_size = %d",
                           req, param,  *rcv_size, snd_size);
        }
    }


    return err;
}


static int f_intf_send(struct st_ltr_crate* crate, const uint8_t* buf,  int size) {
    int err = _ETH_CHECK_HND(crate);
    int sended = 0;
    if (!err) {
        t_eth_data *intf = (t_eth_data *)crate->intf_data;
        sended = send(intf->data_sock, buf, size,0);        
        if (sended < 0) {
            if (L_SOCK_LAST_ERR_BLOCK()) {
                sended = 0;
            } else {
                err = LTRSRV_ERR_SOCKET_SEND;
            }
#ifdef LTRD_ETH_DUMP_ENABLED
        } else  {
            uint32_t save_size = sended;
            intf->dump[intf->dump_pos].dir = 1;
            intf->dump[intf->dump_pos].size = sended;
            intf->dump[intf->dump_pos].snd_req = size;
            intf->dump[intf->dump_pos].time = time(NULL);
            if (save_size < DUMP_PACKET_MAX_SIZE)
                save_size = DUMP_PACKET_MAX_SIZE;
            memcpy(intf->dump[intf->dump_pos].data, buf,
                    save_size);
            DUMP_NEXT_POS(intf->dump_pos);
#endif
        }
    }
    return err ? err : sended;
}

static int f_intf_wait_send_rdy(struct st_ltr_crate *crate, t_ltr_crate_snd_rdy_cb cb) {
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        t_eth_data *intf = (t_eth_data *)crate->intf_data;
        err = ltrsrv_wait_add_sock(intf->data_sock, LTRSRV_WAITFLG_SEND | LTRSRV_WAITFLG_RECV, 0,
                             f_data_cb, crate);
        if (!err)
            intf->snd_rdy_cb = cb;
    }
    return err;
}



/** Закрытие соединения с крейтом. Зачищаем всю память для связи с крейтом. */
static void f_intf_close(t_ltr_crate *crate, int close_by_err) {
    int err = _ETH_CHECK_HND(crate);
    if (!err) {
        t_eth_data *intf = (t_eth_data *)crate->intf_data;
        t_ltr_crate_ip_entry *ip_entry = f_get_crate_ipentry(crate);

        /* удаляем сокеты из очереди ожидания и закрываем их */
        if (intf->cmd_sock!=L_INVALID_SOCKET) {
            ltrsrv_wait_remove_sock(intf->cmd_sock);
            closesocket(intf->cmd_sock);
            intf->cmd_sock = L_INVALID_SOCKET;
        }

        if (intf->data_sock!=L_INVALID_SOCKET) {
            ltrsrv_wait_remove_sock(intf->data_sock);
            closesocket(intf->data_sock);
            intf->data_sock = L_INVALID_SOCKET;
        }

        /* очищаем буферы для обмена командами */
        free(intf->reply);
        free(intf->req);

        ltrsrv_crate_unregister(crate);

        if (ip_entry) {
            ip_entry->crate = NULL;
            ip_entry->status = close_by_err ? LTR_CRATE_IP_STATUS_ERROR : LTR_CRATE_IP_STATUS_OFFLINE;
            /* при закрытии по ошибке, запускаем таймер на переповтор соединения */
            if (close_by_err)
                ltimer_set(&ip_entry->recon_tmr, LTIMER_MS_TO_CLOCK_TICKS(f_eth_reconnect_time));
        }

        free(crate->intf_data);
        crate->intf_data = 0;

        free(crate);
    }
}



/***************************************************************************//**
  Функция запускает процедуру подключения к крейту по Ethernet через TCP
    и цепочку функций инициализации
    @param[in] addr     Адрес в виде строки (наприме 192.168.12.30)
    @return             Код ошибки
    ***************************************************************************/
static int f_eth_con_start(t_ltr_crate_ip_entry *ip_entry, int recon) {
    int err = 0, wt=0;
    t_ltr_crate *crate = NULL;
    t_eth_data *intf = NULL;

    if (recon) {
        if (ip_entry->recon_cnt != 0xFFFFFFFF)
            ip_entry->recon_cnt++;
    } else {
        ip_entry->recon_cnt = 0;
    }

    /* Выделяем память под новую структуру крейта и заполняем ее значениями
       по-умолчанию */
    if (!err) {
        crate = (t_ltr_crate*)malloc(sizeof(t_ltr_crate));
        if (crate==NULL)
            err = LTRSRV_ERR_MEMALLOC;
    }

    if (!err) {
        uint32_t ip = htonl(ip_entry->ip);
        struct sockaddr_in saddr;
        memset(crate, 0, sizeof(t_ltr_crate));

        crate->descr.snd_size = sizeof(t_ltr_crate_descr);

        /* заполняем поля, относящиеся к текущему интерфейсу */
        crate->par.state = LTR_CRATE_CONSTATE_INITIALIZATION;
        crate->par.intf = LTR_CRATE_IFACE_TCPIP;
        crate->par.mode = LTR_CRATE_MODE_WORK;


        crate->intf_func.rd_regs = f_intf_regs_read;
        crate->intf_func.wr_regs = f_intf_regs_write;
        crate->intf_func.ioctl   = f_intf_ioctl;
        crate->intf_func.send    = f_intf_send;
        crate->intf_func.wt_snd_rdy = f_intf_wait_send_rdy;
        crate->intf_func.close   = f_intf_close;
        crate->intf_func.reopen = NULL;
        crate->intf_func.poll_time = LTIMER_MS_TO_CLOCK_TICKS(f_eth_poll_time);
        crate->intf_func.poll_ioctl = LTR_ETH_CMD_POLL_STAT;

        ltimer_set(&crate->intf_func.poll_timer, crate->intf_func.poll_time );


        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = ip;
        saddr.sin_port = 0;


        err = ltrsrv_addr_to_string((struct sockaddr*)&saddr, sizeof(saddr), crate->par.location, LTR_CRATE_LOCATION_SIZE);
    }

    if (!err) {
        crate->intf_data = intf = (t_eth_data*)malloc(sizeof(t_eth_data));
        if (crate->intf_data==NULL)
            err = LTRSRV_ERR_MEMALLOC;
    }

    if (!err) {
        memset(intf, 0, sizeof(t_eth_data));
        intf->cmd_sock = L_INVALID_SOCKET;
        intf->data_sock = L_INVALID_SOCKET;
        intf->req = NULL;
        intf->reply = NULL;


        ip_entry->crate = crate;
        ip_entry->status = LTR_CRATE_IP_STATUS_CONNECTING;
    }

    if (!err) {
        ltrsrv_log_make_crate_srcstr(crate);
        if (ip_entry->recon_cnt == 0) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "Start connection");
        } else {
            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, 0, "Start reconnection (reconnection number %d)", ip_entry->recon_cnt);
        }

        err = f_sock_start_conn(&intf->cmd_sock, AF_INET, crate->par.location,
                                LTR_TCP_CTRL_PORT, &wt, crate, f_ctl_cb);
    }

    if (!err) {
        if (wt) {
            intf->state = ETH_INIT_STATE_CTL_CH_CONN;
        } else {
            /* соединение завершилось сразу! */
            intf->state = ETH_INIT_STATE_INITIALIZATION;
            err = f_start_ctl_req(crate, LTR_ETH_CMD_INIT, 0,
                                  LTR_CRATE_FPGA_INFO_SIZE_MAX*2, 0, NULL);
            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, crate->log_str, 0, "Control socket connection done");
        }
    }


    if (err) {
        if (err != LTRSRV_ERR_MEMALLOC) {
            f_print_connection_err(crate, err, "Control socket connection failed");
        } else {
            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_CRATE_INIT, err, "");
        }
        f_intf_close(crate, err);
    }

    return err;
}


/** Добавление статической записи для IP-адреса */
int ltrsrv_eth_add_ip_entry(uint32_t addr, uint32_t flags, int permanent, int modify) {
    int i, fnd, err = 0;    

    /* если запись уже есть, то просто меняем флаги */
    for (i=0, fnd=0; (i < f_ip_cnt) && !fnd; i++) {
        if (f_ip_entries[i].ip == addr) {
            /* если запись не постаянная, то меняем только текущие флаги, а если
               постоянная - то и флаги, которые могут быть сохранены */
            f_ip_entries[i].flags = flags;
            f_ip_entries[i].st_flags &= ~_IP_ENTRY_STFLAG_NOT_USE; /*добавленную запись всегда используем */
            if (permanent) {
                f_ip_entries[i].save_flags = flags;
                f_ip_entries[i].st_flags |= _IP_ENTRY_STFLAG_SAVE;
                if (modify)
                    ltrsrv_settings_mark_modified();
            }

            fnd = 1;
        }
    }

    if (!fnd) {
        if (f_ip_cnt == LTRSRV_ETH_STATIC_IP_ENTRY_CNT) {
            err = LTRSRV_ERR_ETH_EXCEED_MAX_IP_ENTRY_CNT;
        } else {
            f_ip_entries[f_ip_cnt].ip = addr;
            f_ip_entries[f_ip_cnt].save_flags = f_ip_entries[f_ip_cnt].flags = flags;
            f_ip_entries[f_ip_cnt].status = LTR_CRATE_IP_STATUS_OFFLINE;
            f_ip_entries[f_ip_cnt].crate = NULL;
            f_ip_entries[f_ip_cnt].st_flags = permanent ? _IP_ENTRY_STFLAG_SAVE : 0;
            f_ip_entries[f_ip_cnt].recon_cnt = 0;
            if (permanent && modify)
                ltrsrv_settings_mark_modified();
            f_ip_cnt++;
        }
    }
    return err;
}


int ltrsrv_eth_rem_ip_entry(uint32_t addr, int permanent, int modify) {
    int i, fnd, err = 0;

    /* если запись уже есть, то просто меняем флаги */
    for (i=0, fnd=0; (i < f_ip_cnt) && !fnd; i++) {
        if (f_ip_entries[i].ip == addr) {
            fnd = 1;
            if ((f_ip_entries[i].status == LTR_CRATE_IP_STATUS_OFFLINE)
                    || (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_ERROR)) {
                /* если удаляем на постоянной основе (или если удаляем временную запись)
                   - то убираем запись из массива */
                if (permanent || !(f_ip_entries[i].st_flags &_IP_ENTRY_STFLAG_SAVE)) {

                    if (i!=(f_ip_cnt-1))
                        memmove(&f_ip_entries[i], &f_ip_entries[i+1], (f_ip_cnt-1-i)*sizeof(f_ip_entries[0]));
                    f_ip_cnt--;

                    if (permanent && modify)
                        ltrsrv_settings_mark_modified();
                } else {
                    /* иначе - просто помечаем, что запись не используется больше */
                    f_ip_entries[i].st_flags |= _IP_ENTRY_STFLAG_NOT_USE;
                }
            } else {
                err = LTRSRV_ERR_ETH_CRATE_CONNECTED;
            }
        }
    }
    return err;
}


static void ltrsrv_eth_start_recon(void) {
    int i = 0;

    for (i = 0; i < f_ip_cnt; i++) {
        if ((f_ip_entries[i].flags & LTR_CRATE_IP_FLAG_RECONNECT)
                && !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)
                && (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_ERROR)
                && ltimer_expired(&f_ip_entries[i].recon_tmr)) {
            f_eth_con_start(&f_ip_entries[i], 1);
        }
    }
}


int ltrsrv_eth_start_autocon(void) {
    int i, err =0;

    for (i=0; i < f_ip_cnt; i++) {
        if ((f_ip_entries[i].flags & LTR_CRATE_IP_FLAG_AUTOCONNECT)
                && !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            /* крейты в процессе подключения нужно будет переподключить.
               закрываем соединение и запись переходит в состояние LTR_CRATE_IP_STATUS_OFFLINE,
               и выполняется следующая проверка */
            if (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_CONNECTING) {
                f_intf_close(f_ip_entries[i].crate, 0);
            }

            if ((f_ip_entries[i].status == LTR_CRATE_IP_STATUS_OFFLINE)
                    || (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_ERROR)) {
                int start_err = f_eth_con_start(&f_ip_entries[i], 0);
                if (!err)
                    err = start_err;
            }             
        }
    }
    return err;
}


int ltrsrv_eth_get_ip_entry_list(t_ltr_crate_client_ip_entry* lst, int max_cnt,
                                    uint32_t msk, uint32_t addr, uint32_t *fnd_cnt) {
    int _fnd_cnt = 0, i;

    for (i=0; i < f_ip_cnt; i++) {
        if (((f_ip_entries[i].ip & msk) == (addr & msk)) &&
                !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            if (_fnd_cnt < max_cnt) {
                lst[_fnd_cnt].flags = f_ip_entries[i].flags;
                lst[_fnd_cnt].ip_addr = f_ip_entries[i].ip;

                if (f_ip_entries[i].crate) {
                    memcpy(lst[_fnd_cnt].serial_number, f_ip_entries[i].crate->descr.serial, LTR_CRATE_SERIAL_SIZE);
                } else {
                    memset(lst[_fnd_cnt].serial_number, 0, LTR_CRATE_SERIAL_SIZE);
                }

                lst[_fnd_cnt].status = f_ip_entries[i].status;
                lst[_fnd_cnt].is_dynamic = 0;
            }
            _fnd_cnt++;
        }
    }

    if (fnd_cnt!=NULL)
        *fnd_cnt = _fnd_cnt;

    return 0;
}

/** Получение следующей IP-записи для сохранения.
    @param[in]  start_ind   Номер записи, за которой следует искать (если ищется первая запись,
                            то нужно указать -1)
    @param[out] entry       В этой переменной возвращаются параметры записи, если
                            они были найденны
    @return                 -1, если запись не найденна
                            номер найденной записи (>=0), если найденна */
int ltrsrv_get_next_saved_ip_entry(int start_ind, t_ltr_crate_client_ip_entry *entry) {
    int i;
    int fnd_i = -1;
    for (i=start_ind+1; (i < f_ip_cnt) && (fnd_i==-1); i++) {
        if (f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_SAVE) {
            entry->flags = f_ip_entries[i].flags;
            entry->ip_addr = f_ip_entries[i].ip;
            if (f_ip_entries[i].crate) {
                memcpy(entry->serial_number, f_ip_entries[i].crate->descr.serial, LTR_CRATE_SERIAL_SIZE);
            } else {
                memset(entry->serial_number, 0, LTR_CRATE_SERIAL_SIZE);
            }
            entry->status = f_ip_entries[i].status;
            entry->is_dynamic = 0;
            fnd_i = i;
        }
    }
    return fnd_i;
}


/** Запуск подключения по IP-записи с соответствующим адресом
    @param addr[in]  IP-адрес для подключения
    @return          Код ошибки */
int ltrsrv_eth_ip_conn(uint32_t addr) {
    int i, fnd, err = 0;

    for (i=0, fnd=0; (i < f_ip_cnt) && !fnd; i++) {
        if ((f_ip_entries[i].ip == addr)
                && !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            fnd=1;
            /* если крейт не подключен, то пробуем подключить.
               иначе итак выполняется команда и возвращаем сразу успех */
            if ((f_ip_entries[i].status == LTR_CRATE_IP_STATUS_OFFLINE) ||
                    (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_ERROR)) {                
                err = f_eth_con_start(&f_ip_entries[i], 0);
            }
        }
    }

    if (!fnd)
        err = LTRSRV_ERR_ETH_INVALID_IP_ENTRY;
    return err;
}

int ltrsrv_eth_ip_disc(uint32_t addr) {
    int i, fnd, err = 0;

    for (i=0, fnd=0; (i < f_ip_cnt) && !fnd; i++) {
        if ((f_ip_entries[i].ip == addr) &&
                !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            fnd=1;
            if ((f_ip_entries[i].status != LTR_CRATE_IP_STATUS_OFFLINE) &&
                    (f_ip_entries[i].status != LTR_CRATE_IP_STATUS_ERROR)) {
                f_intf_close(f_ip_entries[i].crate, 0);
            } else {
                /* если была ошибка то при явной команде disconnect переходим в
                   состояние offline */
                f_ip_entries[i].status = LTR_CRATE_IP_STATUS_OFFLINE;
            }
        }
    }

    if (!fnd)
        err = LTRSRV_ERR_ETH_INVALID_IP_ENTRY;
    return err;
}

int ltrsrv_eth_ip_disc_all(void)  {
    int i;
    for (i=0; i < f_ip_cnt; i++) {
        if ((f_ip_entries[i].status != LTR_CRATE_IP_STATUS_OFFLINE) &&
                (f_ip_entries[i].status != LTR_CRATE_IP_STATUS_ERROR)) {
            f_intf_close(f_ip_entries[i].crate, 0);
        } else {
            f_ip_entries[i].status = LTR_CRATE_IP_STATUS_OFFLINE;
        }
    }
    return 0;
}

int ltrsrv_eth_ip_set_flags(uint32_t addr, uint32_t flags, int permanent, int modify) {
    int i, fnd, err = 0;

    /* если запись уже есть, то просто меняем флаги */
    for (i=0, fnd=0; (i < f_ip_cnt) && !fnd; i++)  {
        if ((f_ip_entries[i].ip == addr) &&
                !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            /* если изменение постоянное, то меняем только текущие флаги, а если
               постоянная - то и флаги, которые могут быть сохранены */
            if (permanent) {
                if (f_ip_entries[i].save_flags!=flags) {
                    f_ip_entries[i].save_flags = flags;
                    if (modify & (f_ip_entries[i].st_flags &_IP_ENTRY_STFLAG_SAVE))
                        ltrsrv_settings_mark_modified();
                }
            }

            if (!err)
                f_ip_entries[i].flags = flags;

            fnd = 1;
        }
    }

    if (!fnd)
        err = LTRSRV_ERR_ETH_INVALID_IP_ENTRY;
    return err;
}


static int f_update_sock_nodelay(t_ltr_crate *crate, void *data) {
    if (crate->par.intf == LTR_CRATE_IFACE_TCPIP) {
        if (crate->par.state != LTR_CRATE_CONSTATE_CLOSED) {
            t_eth_data *intf = (t_eth_data *)crate->intf_data;
            f_sock_set_nodelay(intf->cmd_sock, f_eth_nodelay);
            f_sock_set_nodelay(intf->data_sock, f_eth_nodelay);
        }
    }
    return 0;
}

/***************************************************************************//**
  Установка параметров интерфейса ethernet
    @param[in] param   Параметр из #t_ltrd_eth_params
    @param[in] val     Указатель переменную с устанавливаемым значением
    @param[in] size    Размер значения
    @return            Код ошибки
 ******************************************************************************/
int ltrsrv_eth_set_param(t_ltrd_params param, const void *val, uint32_t size) {
    int err = 0;
    uint32_t uintval;
    switch (param) {
        case LTRD_PARAM_ETH_CRATE_POLL_TIME:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                int i;

                /* при смене таймаута опроса перезапускаем таймеры всех крейтов */
                f_eth_poll_time = uintval;
                if ((f_eth_poll_time!=0) && (f_eth_poll_time< LTRSRV_ETH_CRATE_POLL_INTERVAL_MIN))
                    f_eth_poll_time = LTRSRV_ETH_CRATE_POLL_INTERVAL_MIN;

                ltrsrv_settings_mark_modified();

                for (i=0; i < f_ip_cnt; i++) {
                    if (f_ip_entries[i].crate!=NULL) {
                        ltimer_set(&f_ip_entries[i].crate->intf_func.poll_timer,
                                   LTIMER_MS_TO_CLOCK_TICKS(f_eth_poll_time));
                        f_ip_entries[i].crate->intf_func.poll_time = f_eth_poll_time;
                    }
                }
            }
            break;
        case LTRD_PARAM_ETH_CRATE_CON_TOUT:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                f_eth_con_timeout = MAX(uintval, LTRSRV_ETH_CONNECTION_TOUT_MIN);
                ltrsrv_settings_mark_modified();
            }
            break;
        case LTRD_PARAM_ETH_CRATE_CTLCMD_TOUT:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                f_eth_cmd_timeout = MAX(uintval, LTRSRV_ETH_CTL_CMD_TOUT_MIN);
                ltrsrv_settings_mark_modified();
            }
            break;
        case LTRD_PARAM_ETH_INTF_CHECK_TIME:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                f_eth_intf_check_time = uintval;
                ltrsrv_settings_mark_modified();
                if ((f_eth_intf_check_time != 0) &&
                        (f_eth_intf_check_time < LTR_SERVER_DEFAULT_ETH_INTF_CHECK_TIME_MIN)) {
                    f_eth_intf_check_time = LTR_SERVER_DEFAULT_ETH_INTF_CHECK_TIME_MIN;
                }

                if (f_eth_intf_check_time!=0) {
                    ltrsrv_wait_add_tmr(&f_hostaddrs, 0, f_eth_intf_check_time,
                                        f_intf_check_tout_cb, NULL);
                } else {
                    ltrsrv_wait_remove_tmr(&f_hostaddrs);
                }
            }
            break;
        case LTRD_PARAM_ETH_RECONNECTION_TIME:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                f_eth_reconnect_time = uintval;
                ltrsrv_settings_mark_modified();
                if ((f_eth_reconnect_time != 0) &&
                        (f_eth_reconnect_time < LTRSRV_ETH_RECONNECT_TOUT_MIN)) {
                    f_eth_reconnect_time = LTRSRV_ETH_RECONNECT_TOUT_MIN;
                }

                if (f_eth_reconnect_time!=0) {
                    ltrsrv_wait_add_tmr(&f_eth_reconnect_time, 0, f_eth_reconnect_time/2,
                                        f_check_recon_cb, NULL);
                } else {
                    ltrsrv_wait_remove_tmr(&f_eth_reconnect_time);
                }
            }
            break;
    case LTRD_PARAM_ETH_SEND_NODELAY:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                int setval = !!uintval;
                if (setval != f_eth_nodelay) {
                    f_eth_nodelay = setval;
                    ltrsrv_settings_mark_modified();
                    err = ltrsrv_crates_exec_for_all(f_update_sock_nodelay, NULL);
                }
            }
            break;
        default:
            err = LTRSRV_ERR_INVALID_PARAM;
            break;
    }

    return err;
}




/***************************************************************************//**
  Установка параметров интерфейса ethernet
    @param[in] param   Параметр из #t_ltrd_eth_params
    @param[in] val     Указатель переменную с устанавливаемым значением
    @param[in] size    Размер значения
    @return            Код ошибки
 ******************************************************************************/
int ltrsrv_eth_get_param(t_ltrd_params param, void *val, uint32_t *size) {
    int err = 0;
    switch (param) {
        case LTRD_PARAM_ETH_CRATE_POLL_TIME:
            err = ltrsrv_param_put_uint32(val, size, f_eth_poll_time);
            break;
        case LTRD_PARAM_ETH_CRATE_CON_TOUT:
            err = ltrsrv_param_put_uint32(val, size, f_eth_con_timeout);
            break;
        case LTRD_PARAM_ETH_CRATE_CTLCMD_TOUT:
            err = ltrsrv_param_put_uint32(val, size, f_eth_cmd_timeout);
            break;
        case LTRD_PARAM_ETH_INTF_CHECK_TIME:
            err = ltrsrv_param_put_uint32(val, size, f_eth_intf_check_time);
            break;
        case LTRD_PARAM_ETH_RECONNECTION_TIME:
            err = ltrsrv_param_put_uint32(val, size, f_eth_reconnect_time);
            break;
        case LTRD_PARAM_ETH_SEND_NODELAY:
            err = ltrsrv_param_put_uint32(val, size, (uint32_t)f_eth_nodelay);
            break;
        default:
            err = LTRSRV_ERR_INVALID_PARAM;
            break;
    }

    return err;
}


static void f_hostaddr_ch(void) {
    int i;
    for (i=0; i < f_ip_cnt; i++) {
        if ((f_ip_entries[i].flags & LTR_CRATE_IP_FLAG_AUTOCONNECT)
                && !(f_ip_entries[i].st_flags & _IP_ENTRY_STFLAG_NOT_USE)) {
            /* крейты в процессе подключения нужно будет переподключить.
               закрываем соединение и запись переходит в состояние LTR_CRATE_IP_STATUS_OFFLINE,
               и выполняется следующая проверка */
            if (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_CONNECTING) {
                f_intf_close(f_ip_entries[i].crate, 0);
            }

            if ((f_ip_entries[i].status == LTR_CRATE_IP_STATUS_OFFLINE)
                    || (f_ip_entries[i].status == LTR_CRATE_IP_STATUS_ERROR)) {                
                f_eth_con_start(&f_ip_entries[i], 0);
            }
        }
    }
}



static void f_intf_check_tout_cb(t_socket sock, int event, void *data) {
    if (event == LTRSRV_EVENT_TOUT) {
        f_check_intf();
    }
}

static void f_check_recon_cb(t_socket sock, int event, void *data) {
    if (event == LTRSRV_EVENT_TOUT) {
        ltrsrv_eth_start_recon();
    }
}

/** Переодически вызываемая функция.
    В ней мы просматриваем список доступных адресов для хоста.
    При появлении нового адреса - пробуем запустить все крейты с настройкой
    автозапуска, для которых еще не установленно соединение */
static void f_check_intf(void) {
    int new_addr_appears = 0;
#ifdef _WIN32
    static int ind = 0;
    static ULONG ip_list_size[2] = {0,0};
    PIP_ADAPTER_INFO prev_ip_list;
    PULONG pListSize;
    int err = 0;
    DWORD ret_val = 0;


    prev_ip_list = f_hostaddrs[ind];
    ind ^= 1;
    pListSize = &ip_list_size[ind];

    // Если не хватает размера буфера - то вернется ошибка ERROR_BUFFER_OVERFLOW
    // и нужно выделить буфер нового размера
    ret_val = GetAdaptersInfo(f_hostaddrs[ind], pListSize);
    if (ret_val == ERROR_BUFFER_OVERFLOW) {
        free(f_hostaddrs[ind]);
        f_hostaddrs[ind] = (IP_ADAPTER_INFO *) malloc(*pListSize);
        if (f_hostaddrs[ind] == NULL) {
            err = LTRSRV_ERR_MEMALLOC;
        } else {
            ret_val = GetAdaptersInfo(f_hostaddrs[ind], pListSize);
        }
    }

    /* проходимся по всем адапторам и всем адресам */
    if (!err && (ret_val==ERROR_SUCCESS)) {
        PIP_ADAPTER_INFO pAdapter = f_hostaddrs[ind];
        while (pAdapter) {
            PIP_ADDR_STRING ip_addr = &pAdapter->IpAddressList;
            while (ip_addr) {
                /* проверяем только действительные адреса */
                if (strcmp(ip_addr->IpAddress.String, "0.0.0.0")) {
                    PIP_ADAPTER_INFO prevAdapter = prev_ip_list;
                    int fnd_adapter = 0, fnd_addr = 0;
                    while (prevAdapter && !fnd_adapter) {
                        if (!strcmp(prevAdapter->AdapterName, pAdapter->AdapterName)) {
                            PIP_ADDR_STRING prev_ip_addr = &prevAdapter->IpAddressList;
                            fnd_adapter = 1;

                            while (prev_ip_addr && !fnd_addr) {
                                if (!strcmp(prev_ip_addr->IpAddress.String,
                                            ip_addr->IpAddress.String)) {
                                    fnd_addr = 1;
                                }
                                prev_ip_addr = prev_ip_addr->Next;
                            }
                        }

                        prevAdapter = prevAdapter->Next;
                    }

                    if (!fnd_addr) {
                        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, LTRSRV_LOGSRC_SERVER, 0,
                                       "New host address: intf: %s, addr %s, msk %s",
                                       pAdapter->AdapterName,
                                       ip_addr->IpAddress.String,
                                       ip_addr->IpMask.String);
                        new_addr_appears = 1;
                    }
                }
                ip_addr = ip_addr->Next;
            }
            pAdapter = pAdapter->Next;
        }
    }

#else
    struct ifaddrs *ifaddr_lst=NULL, *ifaddr=NULL;    
    int ret;

    ret = getifaddrs(&ifaddr_lst);
    if (ret==0) {

        for (ifaddr = ifaddr_lst; ifaddr!=NULL; ifaddr = ifaddr->ifa_next) {
            if ((ifaddr->ifa_addr != NULL) && (ifaddr->ifa_addr->sa_family == AF_INET)) {
                struct sockaddr_in* addr = (struct sockaddr_in*)ifaddr->ifa_addr;
                struct sockaddr_in* msk = (struct sockaddr_in*)ifaddr->ifa_netmask;

                /* loopback интерфейс не проверяем */
                if (addr->sin_addr.s_addr != 0x100007f) {
                    /* проверяем, была ли информация по указанному интерфейсу */
                    struct ifaddrs *hostaddr=NULL, *fnd_hostaddr=NULL;

                    for (hostaddr = f_hostaddrs; (hostaddr!=NULL) && (fnd_hostaddr==NULL);
                         hostaddr = hostaddr->ifa_next) {
                        if ((hostaddr->ifa_addr != NULL) && (hostaddr->ifa_addr->sa_family == AF_INET)) {
                            struct sockaddr_in* haddr = (struct sockaddr_in*)hostaddr->ifa_addr;
                            if ((((hostaddr->ifa_name != NULL) && (ifaddr->ifa_name != NULL)
                                 && !strcmp(hostaddr->ifa_name, ifaddr->ifa_name)) ||
                                    ((hostaddr->ifa_name == NULL) && (ifaddr->ifa_name == NULL))) &&
                                    (haddr->sin_addr.s_addr == addr->sin_addr.s_addr)) {
                                fnd_hostaddr = hostaddr;
                            }
                        }
                    }

                    /* если новый интерфейс - делаем автоконнект для него */
                    if (fnd_hostaddr==NULL) {
                        char str_addr[20], str_msk[20];
                        str_msk[0]=0;

                        inet_ntop(addr->sin_family, &addr->sin_addr, str_addr, sizeof(str_addr));
                        if (msk!=NULL)
                            inet_ntop(msk->sin_family, &msk->sin_addr, str_msk, sizeof(str_msk));



                        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, LTRSRV_LOGSRC_SERVER,0,
                                       "New host address: intf name: %s, addr %s, msk %s",
                                       ifaddr->ifa_name == NULL ? "" : ifaddr->ifa_name,
                                       str_addr, str_msk);
                        new_addr_appears = 1;
                    }
                }
            }
        }
    }

    if (f_hostaddrs!=NULL) {
        freeifaddrs(f_hostaddrs);
    }
    f_hostaddrs = ifaddr_lst;
#endif
    if (new_addr_appears) {
        f_hostaddr_ch();
    }
}


int ltrsrv_eth_init(void) {
    f_check_intf();
    if (f_eth_intf_check_time != 0)
        ltrsrv_wait_add_tmr(&f_hostaddrs, 0, f_eth_intf_check_time, f_intf_check_tout_cb, NULL);
    if (f_eth_reconnect_time != 0)
        ltrsrv_wait_add_tmr(&f_eth_reconnect_time, 0, f_eth_reconnect_time/2, f_check_recon_cb, NULL);
    return 0;
}

int ltrsrv_eth_close(void) {
#ifdef _WIN32
    free(f_hostaddrs[0]); f_hostaddrs[0] = NULL;
    free(f_hostaddrs[1]); f_hostaddrs[1] = NULL;
#endif

#ifdef HAVE_IFADDRS_H
    if (f_hostaddrs!=NULL) {
        freeifaddrs(f_hostaddrs);
        f_hostaddrs=NULL;
    }
#endif
    ltrsrv_wait_remove_tmr(&f_hostaddrs);
    ltrsrv_wait_remove_tmr(&f_eth_reconnect_time);
    return 0;
}




