#include <stdio.h>

#include <string.h>

/* channel.h должен включаться до ltrapi.h на windows (todo: понять почему...) */
#include "channel.h"
#include "ltrapi.h"
#include "ltrmodule.h"
#ifdef LTRAPI_USE_KD_STORESLOTS
#include "ltrslot.h"
#endif


/*================================================================================================*/
#define OPEN_RETRY_TIMEOUT (10000)
#define LTR_INIT_CMD_SIZE (8 + 16 + 2 + 4)

/* получение структуры канала связи по TLTR */
#define LTR_CHANNEL(ltr) ((TLTR_CHANNEL *)ltr->Internal)
/* проверка, что указатель на TLTR действителен и канал связи открыт */
#define LTR_CHECK_OPENED(ltr) (ltr==NULL ? LTR_ERROR_PARAMETERS : (LTR_CHANNEL(ltr)==NULL ? LTR_ERROR_CHANNEL_CLOSED : LTR_OK))
/* получение таймаута с учетом что 0 означает таймаут по-умолчанию */
#define LTR_TIMEOUT(ltr, tout) ((tout) == 0 ? ltr_ch_get_def_tout(LTR_CHANNEL(ltr)) : (tout))


/* посылка управляющего запроса c кодом ошибки LTR_ERROR_EXECUTE при неверном подтверждении */
#define f_StdCtlFuncDef(ltr, req_buf, req_size, reply_buf, reply_size, tmr) \
    (f_StdCtlFunc(ltr, req_buf, req_size, reply_buf, reply_size, tmr, LTR_ERROR_EXECUTE))
#define SIZE_ARRAY(a) (sizeof(a) / sizeof((a)[0]))


/*================================================================================================*/
static LINLINE INT f_get_cmd_ack_err(DWORD ack);
static INT f_SetFPGAConfigRegisters(TLTR *ltr, WORD first_reg, UINT size, LPCVOID values);
static INT f_StdCtlFunc (TLTR *ltr, LPCVOID request_buf, DWORD request_size, LPVOID reply_buf,
    DWORD reply_size, t_ltimer *ptmr, INT ack_error_code);
static INT f_StdCtlFuncEx(TLTR *ltr, DWORD cmd_code, DWORD send_size, DWORD recv_size,
    LPCVOID send_buf, LPVOID recv_buf, DWORD *recvd_size, t_ltimer *ptmr);


/*================================================================================================*/
#ifdef _WIN32
int ___CPPdebugHook = 0;
#endif
static const TLTR_ERROR_STRING_DEF ErrorStrings[] = {
    { LTR_OK,                           "Выполнено без ошибок" },
    { LTR_ERROR_PARAMETERS,             "Ошибка входных параметров" },
    { LTR_ERROR_MEMORY_ALLOC,           "Ошибка динамического выделения памяти" },
    { LTR_ERROR_OPEN_CHANNEL,           "Ошибка инициализации канала обмена с ltrd" },
    { LTR_ERROR_OPEN_SOCKET,            "Ошибка подключения к ltrd" } ,
    { LTR_ERROR_CHANNEL_CLOSED,         "Канал обмена с ltrd не был создан или закрыт" },
    { LTR_ERROR_SEND,                   "Ошибка передачи данных в ltrd" },
    { LTR_ERROR_RECV,                   "Ошибка приема данных от ltrd" },
    { LTR_ERROR_EXECUTE,                "Ошибка обмена с крейт-контроллером" },
    { LTR_WARNING_MODULE_IN_USE,        "Предупреждение: уже создано активное соединение с данным модулем" },
    { LTR_ERROR_NOT_CTRL_CHANNEL,       "Данная операция доступна только для управляющего соединения" },
    { LTR_ERROR_SRV_INVALID_CMD,        "Команда не поддерживается ltrd"},
    { LTR_ERROR_SRV_INVALID_CMD_PARAMS, "ltrd не поддерживает указанные параметры команды"},
    { LTR_ERROR_INVALID_CRATE,          "Указанный крейт не найден"},
    { LTR_ERROR_EMPTY_SLOT,             "В указанном слоте отсутствует модуль"},
    { LTR_ERROR_UNSUP_CMD_FOR_SRV_CTL,  "Команда не поддерживается управляющим соединением"},
    { LTR_ERROR_INVALID_IP_ENTRY,       "Неверная запись сетевого адреса крейта"},    
    { LTR_ERROR_NOT_IMPLEMENTED,        "Данная возможность не реализована"},
    { LTR_ERROR_CONNECTION_CLOSED,      "Соединение было закрыто службой ltrd"},
    { LTR_ERROR_LTRD_UNKNOWN_RETCODE,   "Неизвестный код ошибки при выполнении команды ltrd"},
    { LTR_ERROR_LTRD_CMD_FAILED,        "Ошибка выполнения управляющей команды ltrd"},
    { LTR_ERROR_INVALID_CON_SLOT_NUM,   "Указан неверный номер слота при открытии соединения"},
    { LTR_ERROR_INVALID_MODULE_DESCR,   "Неверный описатель модуля"},
    { LTR_ERROR_INVALID_MODULE_SLOT,    "Неверный номер слота в крейте"},
    { LTR_ERROR_INVALID_MODULE_ID,      "Неверный модуль в слоте"},
    { LTR_ERROR_NO_RESET_RESPONSE,      "Нет ответа на сброс модуля"},
    { LTR_ERROR_SEND_INSUFFICIENT_DATA, "Передано меньше слов в модуль, чем запрашивалось"},
    { LTR_ERROR_RECV_INSUFFICIENT_DATA, "Принято меньше слов от модуля, чем запрашивалось"},
    { LTR_ERROR_NO_CMD_RESPONSE,        "Нет ответа на переданную команду"},
    { LTR_ERROR_INVALID_CMD_RESPONSE,   "Пришел неверный ответ на команду"},
    { LTR_ERROR_INVALID_RESP_PARITY,    "Ошибка четности в принятом ответе на команду"},
    { LTR_ERROR_INVALID_CMD_PARITY,     "Ошибка четности в переданной команде"},
    { LTR_ERROR_UNSUP_BY_FIRM_VER,      "Возможность не поддерживается данной версией прошивки"},
    { LTR_ERROR_MODULE_STARTED,         "Операция не допустима при запущенном сборе данных"},
    { LTR_ERROR_MODULE_STOPPED,         "Сбор данных остановлен"},
    { LTR_ERROR_RECV_OVERFLOW,          "Произошло переполнение буфера службы ltrd при приеме данных"},
    { LTR_ERROR_FIRM_FILE_OPEN,         "Ошибка открытия файла прошивки"},
    { LTR_ERROR_FIRM_FILE_READ,         "Ошибка чтения файла прошивки"},
    { LTR_ERROR_FIRM_FILE_FORMAT,       "Неверный формат файла прошивки"},
    { LTR_ERROR_FPGA_LOAD_READY_TOUT,   "Превышен таймаут ожидания готовности ПЛИС к загрузке"},
    { LTR_ERROR_FPGA_LOAD_DONE_TOUT,    "Превышен таймаут ожидания перехода ПЛИС в рабочий режим"},
    { LTR_ERROR_FPGA_IS_NOT_LOADED,     "Прошивка ПЛИС не загружена"},
    { LTR_ERROR_FLASH_INVALID_ADDR,     "Неверный адрес flash-памяти"},
    { LTR_ERROR_FLASH_WAIT_RDY_TOUT,    "Превышен таймаут ожидания завершения записи или стирания Flash-памяти"},
    { LTR_ERROR_FIRSTFRAME_NOTFOUND,    "Не найдено начало кадра данных в потоке от модуля"},
    { LTR_ERROR_CARDSCONFIG_UNSUPPORTED,"Крейт не поддерживает сохранение конфигурации модулей"},
    { LTR_ERROR_FLASH_OP_FAILED,        "Ошибка выполнения операции с flash-памятью"},
    { LTR_ERROR_FLASH_NOT_PRESENT,      "Flash-память не обнаружена"},
    { LTR_ERROR_FLASH_UNSUPPORTED_ID,   "Обнаружен неподдерживаемый тип flash-памяти"},
    { LTR_ERROR_FLASH_UNALIGNED_ADDR,   "Невыровненный адрес flash-памяти"},
    { LTR_ERROR_FLASH_VERIFY,           "Ошибка при проверке записанных данных во flash-память"},
    { LTR_ERROR_FLASH_UNSUP_PAGE_SIZE , "Установлен неподдерживаемый размер страницы flash-памяти"},
    { LTR_ERROR_FLASH_INFO_NOT_PRESENT, "Отсутствует информация о модуле во flash-памяти"},
    { LTR_ERROR_FLASH_INFO_UNSUP_FORMAT,"Неподдерживаемый формат информации о модуле во flash-памяти"},
    { LTR_ERROR_FLASH_SET_PROTECTION,   "Не удалось установить защиту flash-памяти"},
    { LTR_ERROR_FPGA_NO_POWER,          "Нет питания микросхемы ПЛИС"},
    { LTR_ERROR_FPGA_INVALID_STATE,     "Недействительное состояние загрузки ПЛИС"},
    { LTR_ERROR_FPGA_ENABLE,            "Не удалось перевести ПЛИС в разрешенное состояние"},
    { LTR_ERROR_FPGA_AUTOLOAD_TOUT,     "Истекло время ожидания автоматической загрузки ПЛИС"},
    { LTR_ERROR_PROCDATA_UNALIGNED,     "Обрабатываемые данные не выравнены на границу кадра"},
    { LTR_ERROR_PROCDATA_CNTR,          "Ошибка счетчика в обрабатываемых данных"},
    { LTR_ERROR_PROCDATA_CHNUM,         "Неверный номер канала в обрабатываемых данных"},
    { LTR_ERROR_PROCDATA_WORD_SEQ,      "Неверная последовательность слов в обрабатываемых данных"},
    { LTR_ERROR_FLASH_INFO_CRC,         "Неверная контрольная сумма в записанной информации о модуле"},
    { LTR_ERROR_PROCDATA_UNEXP_CMD,     "Обнаружена неожиданная команда в потоке данных"},
    { LTR_ERROR_UNSUP_BY_BOARD_REV,     "Возможность не поддерживается данной ревизией платы"},
    { LTR_ERROR_MODULE_NOT_CONFIGURED,  "Не выполнена конфигурация модуля"},
    { LTR_ERROR_FLASH_INFO_DEVTYPE,     "Информация во flash-памяти относится к неверному типу устройства"},
    { LTR_ERROR_OP_DONE_WAIT_TOUT,      "Не удалось дождаться завершения операции за заданное время"},
    { LTR_ERROR_UNSUP_BY_MODIFICATION,  "Возможность не поддерживается данной модификацией модуля"},
    { LTR_ERROR_UNEXP_DATA_AFTER_ACK,   "После подтверждения приняты лишние данные"},
    { LTR_ERROR_UNKNOWN_MODULE_ID,      "Неизвестный идентификатор модуля" }
};
static const struct {
    DWORD ack;
    INT   err;
} f_cmd_ack_errs[] = {
    { LTR_CLIENT_ACK_MODULE_IN_USE,         LTR_WARNING_MODULE_IN_USE},
    { LTR_CLIENT_ACK_GOOD,                  LTR_OK},
    { LTR_CLIENT_ACK_BAD,                   LTR_ERROR_LTRD_CMD_FAILED},
    { LTR_CLIENT_ACK_ERR_INVALID_CMD,       LTR_ERROR_SRV_INVALID_CMD},
    { LTR_CLIENT_ACK_ERR_CRATE_NOT_FOUND,   LTR_ERROR_INVALID_CRATE},
    { LTR_CLIENT_ACK_ERR_EMPTY_SLOT,        LTR_ERROR_EMPTY_SLOT},
    { LTR_CLIENT_ACK_ERR_UNSUP_FOR_SRV_CTL, LTR_ERROR_UNSUP_CMD_FOR_SRV_CTL},
    { LTR_CLIENT_ACK_ERR_INVALID_PARAMS,    LTR_ERROR_SRV_INVALID_CMD_PARAMS},
    { LTR_CLIENT_ACK_ERR_INVALID_IP_ENTRY,  LTR_ERROR_INVALID_IP_ENTRY},
    { LTR_CLIENT_ACK_ERR_INVALID_CRATE_SLOT,LTR_ERROR_INVALID_CON_SLOT_NUM}
};
static const LPCSTR f_unknown_error_string = "Неизвестная ошибка";


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifdef WIN32
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    WSADATA wsaData;
    WORD wVersionRequested;

    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        wVersionRequested = MAKEWORD(2, 2);
        if (WSAStartup(wVersionRequested, &wsaData))
            return FALSE;
        if (wsaData.wVersion != wVersionRequested) {
            WSACleanup();
            return FALSE;
        }
        break;
    case DLL_PROCESS_DETACH:
        WSACleanup();
        break;
    }
    return TRUE;
}
#endif

/*------------------------------------------------------------------------------------------------*/
static LINLINE INT f_get_cmd_ack_err(DWORD ack) {
    unsigned int i;
    INT err = LTR_ERROR_LTRD_UNKNOWN_RETCODE;
    int fnd_ack = 0;

    for (i = 0; (i < SIZE_ARRAY(f_cmd_ack_errs)) && !fnd_ack; i++) {
        if (f_cmd_ack_errs[i].ack == ack) {
            fnd_ack = 1;
            err = f_cmd_ack_errs[i].err;
        }
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_SetFPGAConfigRegisters(TLTR *ltr, WORD first_reg, UINT size, LPCVOID values) {
    return LTR_CratePutArray(ltr, SEL_FPGA_DATA | first_reg, values, size);
}

/*------------------------------------------------------------------------------------------------*/
static INT f_StdCtlFunc (TLTR *ltr, LPCVOID request_buf, DWORD request_size, LPVOID reply_buf,
    DWORD reply_size, t_ltimer *ptmr, INT ack_error_code) {
    /* Общий код для выполнения управляющей функции (только по управляющему каналу)
     * Посылается запрос, принимается слово ACK и затем ответ.
     * ltr - дескриптор LTR
     * request_buf - буфер с запросом на передачу
     * request_size - длина запроса (в байтах)
     * reply_buf - буфер для ответа (или NULL)
     * reply_size - длина ответа (в байтах)
     * tout - таймаут на выполнения всей операции, если 0, то используется таймаут по-умолчанию.
     *   Возвращается оставшееся время
     * ack_error_code - какая ошибка, если ack не GOOD (0 = не использовать ack)
     */
    INT transf_res;
    INT res = LTR_OK;
    t_ltimer loc_tmr;


    if ((ltr == NULL) || (request_buf == NULL) || (request_size == 0)) {
        res = LTR_ERROR_PARAMETERS;
    } else if ((reply_size > 0) && (reply_buf == NULL)) {
        res = LTR_ERROR_PARAMETERS;
    } else if (ltr->Internal == NULL) {
        res = LTR_ERROR_CHANNEL_CLOSED;
    } else if (LTR_CC_CHNUM(ltr->cc)!= LTR_CC_CHNUM_CONTROL) {
        /* передача команд возможна только по управляющему каналу */
        res = LTR_ERROR_NOT_CTRL_CHANNEL;
    }

    if (res == LTR_OK) {
        if (ptmr == NULL) {
            ptmr = &loc_tmr;
            ltimer_set(ptmr, LTIMER_MS_TO_CLOCK_TICKS(ltr_ch_get_def_tout(LTR_CHANNEL(ltr))));
        }

        /* передаем буфер данных с командой */
        transf_res = ltr_ch_send((TLTR_CHANNEL *)ltr->Internal, (LPBYTE)request_buf,
                                 request_size, ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != (INT)request_size) {
            res = LTR_ERROR_SEND_INSUFFICIENT_DATA;
        }
    }

    /* если команда требует подтверждения - пробуем его принять */
    if ((res == LTR_OK) && (ack_error_code != 0)) {
        DWORD ack = 0;
        transf_res = ltr_ch_recv(LTR_CHANNEL(ltr), (LPBYTE)&ack, sizeof(ack), ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != (INT)sizeof(ack)) {
            res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
        } else {
            /* проверяем код ответа */
            res = f_get_cmd_ack_err(ack);
            /* если ответ был BAD => устанавливаем код ошибки,
             * который был передан параметром функции
             */
            if (res == LTR_ERROR_EXECUTE)
                res = ack_error_code;
        }
    }

    /* прием ответа, если он нужен */
    if ((res == LTR_OK) && (reply_size > 0)) {
        transf_res = ltr_ch_recv(LTR_CHANNEL(ltr), (LPBYTE)reply_buf, reply_size, ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != (INT)reply_size) {
            res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
        }
    }

    return res;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_StdCtlFuncEx(TLTR *ltr, DWORD cmd_code, DWORD send_size, DWORD recv_size,
    LPCVOID send_buf, LPVOID recv_buf, DWORD *recvd_size, t_ltimer *ptmr) {


    DWORD sbuf[4];
    INT transf_res;    
    struct {
        DWORD ack;
        DWORD resp_size;
    } resp;
    INT res = LTR_OK;
    t_ltimer loc_tmr;

    if (!ltr || (send_size && !send_buf)) {
        res = LTR_ERROR_PARAMETERS;
    } else if ((recv_size > 0) && !recv_buf) {
        res = LTR_ERROR_PARAMETERS;
    } else if (!ltr->Internal) {
        res = LTR_ERROR_CHANNEL_CLOSED;
    } else if (LTR_CC_CHNUM(ltr->cc) != LTR_CC_CHNUM_CONTROL) {
        /* передача команд возможна только по управляющему каналу */
        res = LTR_ERROR_NOT_CTRL_CHANNEL;
    }
    if (res == LTR_OK) {
        if (ptmr == NULL) {
            ptmr = &loc_tmr;
            ltimer_set(ptmr, LTIMER_MS_TO_CLOCK_TICKS(ltr_ch_get_def_tout(LTR_CHANNEL(ltr))));
        }

        sbuf[0] = CONTROL_COMMAND_START;
        sbuf[1] = cmd_code;
        sbuf[2] = send_size;
        sbuf[3] = recv_size;

        /* передаем буфер с заголовком команды */
        transf_res = ltr_ch_send(LTR_CHANNEL(ltr), (LPBYTE)sbuf, sizeof(sbuf), ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != sizeof(sbuf)) {
            res = LTR_ERROR_SEND;
        }
    }

    /* передаем буфер данных с данными команды */
    if ((res == LTR_OK) && send_size) {
        transf_res = ltr_ch_send(LTR_CHANNEL(ltr), (LPBYTE)send_buf, send_size, ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != (INT)send_size) {
            res = LTR_ERROR_SEND_INSUFFICIENT_DATA;
        }
    }

    if (res == LTR_OK) {
        /* принимаем заголовок ответа */
        transf_res = ltr_ch_recv(LTR_CHANNEL(ltr), (BYTE *)&resp, sizeof(resp), ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != sizeof(resp)) {
            res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
        } else if (resp.resp_size > recv_size) {
            res = LTR_ERROR_RECV;
        }
    }

    if ((res == LTR_OK) && (resp.resp_size!=0)) {
        transf_res = ltr_ch_recv(LTR_CHANNEL(ltr), recv_buf, resp.resp_size, ptmr);
        if (transf_res < 0) {
            res = transf_res;
        } else if (transf_res != (INT)resp.resp_size) {
            res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
        }
    }

    if ((res == LTR_OK) && recvd_size)
        *recvd_size = resp.resp_size;

    return (res == LTR_OK) ? f_get_cmd_ack_err(resp.ack) : res;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR__GenericCtlFunc(TLTR *ltr, LPCVOID request_buf, DWORD request_size,
    LPVOID reply_buf, DWORD reply_size, INT ack_error_code, DWORD timeout) {
    /* Интерфейс к низкоуровневой функции для ltrbootapi и т.п.
     * ltr - дескриптор LTR
     * request_buf - буфер с запросом
     * request_size - длина запроса (в байтах)
     * reply_buf - буфер для ответа (или NULL)
     * reply_size - длина ответа (в байтах)
     * ack_error_code - какая ошибка, если ack не GOOD (0 = не использовать ack)
     * timeout - таймаут, мс
     */
    t_ltimer tmr;
    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(timeout));
    return f_StdCtlFunc(ltr, request_buf, request_size, reply_buf, reply_size, &tmr,
                        ack_error_code);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_AddIPCrate(TLTR *hsrv, DWORD ip_addr, DWORD flags, BOOL permanent) {
    /* Добавление IP-адреса крейта в таблицу (с сохранением в файле конфигурации).
     * Если адрес уже существует, возвращает ошибку.
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_ADD_LIST_ENTRY, 0, 0, 0};
    sbuf[2] = ip_addr;
    sbuf[3] = flags;
    sbuf[4] = permanent;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Close(TLTR *ltr) {
    INT err = ltr==NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        TLTR_CHANNEL *channel = LTR_CHANNEL(ltr);

        if (channel != NULL) {
            ltr_ch_free(channel);
            ltr->Internal = NULL;
        }
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Config(TLTR *hcrate, const TLTR_CONFIG *conf) {
    INT err = conf == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        /* Задание конфигурации цифровых входов и выходов крейта */
        WORD reg_val[5];

        /* регистр 0x09: бит 0 = digout_oe, бит 1 = userio0_oe, бит 2 = userio1_oe */
        reg_val[0] = ((conf->digout_en != 0) ? 0x01 : 0x00) |
            ((conf->userio[0] != LTR_USERIO_DIGOUT) ? 0x02 : 0x00) |
            ((conf->userio[1] != LTR_USERIO_DIGOUT) ? 0x04 : 0x00);
        /* регистр 0x0A: селектор DIGOUT1 */
        reg_val[1] = conf->digout[0];
        /* регистр 0x0B: селектор DIGOUT2 */
        reg_val[2] = conf->digout[1];
        /* регистр 0x0C: бит 0 = 0: userio0 = DIGIN1, 1: userio0 = DIGIN2
         *               бит 4 = 0: userio2 = DIGIN1, 1: userio2 = DIGIN2
         */
        reg_val[3] = ((conf->userio[0] == LTR_USERIO_DIGIN2) ? 0x01 : 0x00) |
            ((conf->userio[2] == LTR_USERIO_DIGIN2) ? 0x10 : 0x00);
        /* регистр 0x0D: бит 0 = 0: userio1 = DIGIN1, 1: userio1 = DIGIN2 */
        reg_val[4] = ((conf->userio[1] == LTR_USERIO_DIGIN2) ? 0x01 : 0x00);

        err = f_SetFPGAConfigRegisters(hcrate, 0x09, sizeof(reg_val), reg_val);
    }
    return err;
}


/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_ConnectAllAutoIPCrates(TLTR *hsrv) {
    /* Установление соединения со всеми IP-крейтами, имеющими флаг "автоподключение"
     * Если вернулось LTR_OK, это значит, что ОТПРАВЛЕНА КОМАНДА установить соединение.
     * Результат можно отследить позже по LTR_GetCrates() или LTR_GetListOfIPCrates()
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_CONNECT_ALL_AUTO};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_ConnectIPCrate(TLTR *hsrv, DWORD ip_addr) {
    /* Установление соединения с IP-крейтом (адрес должен быть в таблице)
     * Если адреса нет, возвращает ошибку. Если крейт уже подключен или идет попытка подключения,
     * возвращает LTR_OK.
     * Если вернулось LTR_OK, это значит, что ОТПРАВЛЕНА КОМАНДА установить соединение.
     * Результат можно отследить позже по LTR_GetCrates() или LTR_GetListOfIPCrates()
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_CONNECT, 0};
    sbuf[2] = ip_addr;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_CrateCustomCtlReq(TLTR *hcrate, DWORD req, DWORD param,
    const BYTE *snd_buf, DWORD snd_size, BYTE *rcv_buf, DWORD rcv_size, DWORD *recvd_size) {

    INT err = ((snd_size != 0) && (snd_buf == NULL)) || ((rcv_size != 0) && (rcv_buf == NULL)) ?
        LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        char *snd_data = malloc(8+snd_size);
        if (snd_data == NULL) {
            err = LTR_ERROR_MEMORY_ALLOC;
        } else {
            snd_data[0] = req & 0xFF;
            snd_data[1] = (req >> 8) & 0xFF;
            snd_data[2] = (req >> 16) & 0xFF;
            snd_data[3] = (req >> 24) & 0xFF;
            snd_data[4] = param & 0xFF;
            snd_data[5] = (param >> 8) & 0xFF;
            snd_data[6] = (param >> 16) & 0xFF;
            snd_data[7] = (param >> 24) & 0xFF;
            memcpy(&snd_data[8], snd_buf, snd_size);
            err = f_StdCtlFuncEx(hcrate, LTR_CLIENT_CMD_SEND_CUSTOM_IOCTL_REQ, 8+snd_size, rcv_size,
                snd_data, rcv_buf, recvd_size, NULL);
        }
        free(snd_data);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_DeleteIPCrate(TLTR *hsrv, DWORD ip_addr, BOOL permanent) {
    /* Удаление IP-адреса крейта из таблицы (и из файла конфигурации, если он статический).
     * Если адреса нет, либо крейт занят, возвращает ошибку.
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_DELETE_LIST_ENTRY, 0, 0};
    sbuf[2] = ip_addr;
    sbuf[3] = permanent;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_DisconnectAllIPCrates(TLTR *hsrv) {
    /* Завершение соединения со всеми IP-крейтами
     * Если вернулось LTR_OK, это значит, что ОТПРАВЛЕНА КОМАНДА завершить соединение.
     * Результат можно отследить позже по LTR_GetCrates() или LTR_GetListOfIPCrates()
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_DISCONNECT_ALL};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_DisconnectIPCrate(TLTR *hsrv, DWORD ip_addr) {
    /* Завершение соединения с IP-крейтом (адрес должен быть в таблице)
     * Если адреса нет, возвращает ошибку. Если крейт уже отключен, возвращает LTR_OK.
     * Если вернулось LTR_OK, это значит, что ОТПРАВЛЕНА КОМАНДА завершить соединение.
     * Результат можно отследить позже по LTR_GetCrates() или LTR_GetListOfIPCrates()
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_DISCONNECT, 0};
    sbuf[2] = ip_addr;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrateDescr(TLTR *hsrv, INT crate_iface, const char *crate_sn,
    TLTR_CRATE_DESCR *descr, DWORD size) {

    char snd_data[LTR_CRATE_SERIAL_SIZE+1];

    if (crate_sn != NULL) {
        strncpy(snd_data, crate_sn, LTR_CRATE_SERIAL_SIZE - 1);
        snd_data[LTR_CRATE_SERIAL_SIZE-1] = '\0';
    } else {
        memset(snd_data, 0, LTR_CRATE_SERIAL_SIZE);
    }
    snd_data[LTR_CRATE_SERIAL_SIZE] = crate_iface;

    return f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_GET_CRATE_DESCR, sizeof(snd_data), size, snd_data,
            descr, NULL, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrateInfo(TLTR *hcrate, TLTR_CRATE_INFO *CrateInfo) {
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_CRATE_TYPE};
    return f_StdCtlFuncDef(hcrate, sbuf, sizeof(sbuf), CrateInfo, sizeof(TLTR_CRATE_INFO), NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrateModules(TLTR *hcrate, WORD *mid) {
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_MODULES};
    return f_StdCtlFuncDef(hcrate, sbuf, sizeof(sbuf), mid, LTR_MODULES_PER_CRATE_MAX * 2, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrateRawData(TLTR *hcrate, DWORD *data, DWORD *tmark, DWORD size,
    DWORD timeout) {

    INT ret_code=LTR_OK;

    if (size > 0) {
        DWORD sbuf[5];
        sbuf[0] = CONTROL_COMMAND_START;
        sbuf[1] = CONTROL_COMMAND_GET_CRATE_RAW_DATA;
        sbuf[2] = size;
        sbuf[3] = timeout;
        sbuf[4] = hcrate->flags & LTR_FLAG_RFULL_DATA;

        ret_code = f_StdCtlFunc(hcrate, sbuf, sizeof(sbuf), NULL, 0, NULL, 0);
        if (ret_code == LTR_OK)
            ret_code = LTR_Recv(hcrate, data, tmark, size, timeout);
    } else {
        DWORD SizeRecv;
        DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_CRATE_RAW_DATA_SIZE};
        ret_code = f_StdCtlFunc(hcrate, sbuf, sizeof(sbuf), &SizeRecv, sizeof(SizeRecv), NULL, 0);
        if (ret_code == LTR_OK)
            ret_code = SizeRecv;
    }

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrates(TLTR *hsrv, BYTE *csn) {
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_CRATES};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), csn, LTR_CRATES_MAX * 16, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCrateStatistic(TLTR *hsrv, INT crate_iface, const char *crate_sn,
    TLTR_CRATE_STATISTIC *stat, DWORD size) {

    char snd_data[LTR_CRATE_SERIAL_SIZE+1];
    if (crate_sn != NULL) {
        strncpy(snd_data, crate_sn, LTR_CRATE_SERIAL_SIZE - 1);
        snd_data[LTR_CRATE_SERIAL_SIZE-1] = '\0';
    } else {
        memset(snd_data, 0, LTR_CRATE_SERIAL_SIZE);
    }
    snd_data[LTR_CRATE_SERIAL_SIZE] = crate_iface;

    return f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_GET_CRATE_STAT, sizeof(snd_data), size, snd_data,
                          stat, NULL, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetCratesEx(TLTR *hsrv, DWORD max_crates, DWORD flags,
                                         DWORD *crates_found, DWORD *crates_returned,
                                         CHAR serials[][LTR_CRATE_SERIAL_SIZE],
                                         TLTR_CRATE_INFO *info_list) {

    INT err = LTR_OK;
    BYTE *recv_buf = NULL;
    DWORD recv_size = 8 + (LTR_CRATE_SERIAL_SIZE + 8) * max_crates;


    if (err == LTR_OK) {
        recv_buf = malloc(recv_size);
        if (recv_buf == NULL)
            err = LTR_ERROR_MEMORY_ALLOC;
    }

    if (err == LTR_OK) {
        err = f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_GET_CRATES_WITH_INFO, sizeof(flags), recv_size, &flags, recv_buf,
            &recv_size, NULL);
    }
    if ((err == LTR_OK) && (recv_size < 8))
        err = LTR_ERROR_RECV_INSUFFICIENT_DATA;

    if (err == LTR_OK) {
        DWORD fnd;
        DWORD ret;
        fnd = recv_buf[0] | ((DWORD)recv_buf[1] << 8) | ((DWORD)recv_buf[2] << 16) |
            ((DWORD)recv_buf[3] << 24);
        ret = recv_buf[4] | ((DWORD)recv_buf[5] << 8) | ((DWORD)recv_buf[6] << 16) |
            ((DWORD)recv_buf[7] << 24);
        if (ret > max_crates)
            ret = max_crates;

        if (recv_size < (8 + ret * (LTR_CRATE_SERIAL_SIZE + 8)))
            err = LTR_ERROR_RECV_INSUFFICIENT_DATA;


        if (err == LTR_OK) {
            BYTE *cur_pos;
            DWORD i;
            for (i = 0, cur_pos = &recv_buf[8]; (i < ret); i++) {
                if (serials != NULL)
                    memcpy(serials[i], cur_pos, LTR_CRATE_SERIAL_SIZE);
                cur_pos += LTR_CRATE_SERIAL_SIZE;
                if (info_list != NULL) {
                    info_list[i].CrateType = cur_pos[0];
                    info_list[i].CrateInterface = cur_pos[1];
                }
                cur_pos += 8;
            }

            if (crates_returned != NULL)
                *crates_returned =ret;
            if (crates_found != NULL)
                *crates_found = fnd;
        }
    }
    free(recv_buf);
    return err;
}

/* Функция оставлена для совместимости, если кто-то ее использовал до того как была
   описана. В описанном интерфейсе сразу появилась LTR_GetCratesEx() с немного
   другим парядком параметров и параметом flags */
LTRAPIWIN_DllExport(INT) LTR_GetCratesWithInfo(TLTR *ltr, CHAR serials[][LTR_CRATE_SERIAL_SIZE],
    TLTR_CRATE_INFO *info_list, DWORD max_size, DWORD *crates_returned, DWORD *crates_found) {
    return LTR_GetCratesEx(ltr, max_size, 0, crates_found, crates_returned, serials, info_list);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(LPCSTR) LTR_GetErrorString(INT err) {
    size_t i;
    for (i = 0; (i < SIZE_ARRAY(ErrorStrings)); i++) {
        if (ErrorStrings[i].code == err)
            return ErrorStrings[i].message;
    }
    return f_unknown_error_string;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetIPCrateDiscoveryMode(TLTR *ltr, BOOL *enabled, BOOL *autoconnect) {
    /* Чтение режима поиска IP-крейтов в локальной сети */
    DWORD rbuf[2];
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_GET_DISCOVERY_MODE};
    INT res = f_StdCtlFuncDef(ltr, sbuf, sizeof(sbuf), rbuf, sizeof(rbuf), NULL);
    if (res == LTR_OK) {
        if (enabled != NULL)
            *enabled = (rbuf[0] != 0);
        if (autoconnect != NULL)
            *autoconnect = (rbuf[1] != 0);
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetListOfIPCrates(TLTR *hsrv, DWORD max_entries, DWORD ip_net,
    DWORD ip_mask, DWORD *entries_found, DWORD *entries_returned, TLTR_CRATE_IP_ENTRY *info_array) {
    /* Получение списка известных серверу IP-крейтов (подключенных и не подключенных)
     * Соответствует содержимому окна сервера "Управление IP-крейтами".
     */    

    INT res = LTR_CHECK_OPENED(hsrv);
    if (res==LTR_OK) {
        DWORD rbuf[2];
        DWORD read_bytes;
        t_ltimer tmr;
        DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_GET_LIST, 0, 0, 0};

        sbuf[2] = max_entries;
        sbuf[3] = ip_net;
        sbuf[4] = ip_mask;

        if (entries_found)
            *entries_found = 0;
        if (entries_returned)
            *entries_returned = 0;
        if (info_array==NULL)
            max_entries = 0;

        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(ltr_ch_get_def_tout(LTR_CHANNEL(hsrv))));
        res = f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), rbuf, sizeof(rbuf), &tmr);
        if (res == LTR_OK) {
            if (rbuf[1] > max_entries) {
                /* если продолжаем выполнение дальше, то зачем это делать ошибкой? */
                /* res = LTR_ERROR_RECV; */
                rbuf[1] = max_entries;
            }
            read_bytes = rbuf[1] * sizeof(TLTR_CRATE_IP_ENTRY);

            if (entries_found)
                *entries_found = rbuf[0];

            if (entries_returned)
                *entries_returned = rbuf[1];

            if (read_bytes > 0) {
                INT recvd = ltr_ch_recv(LTR_CHANNEL(hsrv), (LPBYTE)info_array,
                                read_bytes, &tmr);
                if (recvd < 0) {
                    res = recvd;
                } else if (recvd!= (INT)read_bytes) {
                    res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                }
            }
        }
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetLogLevel(TLTR *hsrv, INT *level) {
    // Чтение уровня журнализации
    DWORD rbuf[1];
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_LOG_LEVEL};
    INT res = f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), rbuf, sizeof(rbuf), NULL);

    if ((res == LTR_OK) && (level != NULL))
        *level = rbuf[0];
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetModuleStatistic(TLTR *hsrv, INT crate_iface, const char *crate_sn, INT slot,
    TLTR_MODULE_STATISTIC *stat, DWORD size) {

    char snd_data[LTR_CRATE_SERIAL_SIZE+4];
    if (crate_sn != NULL) {
        strncpy(snd_data, crate_sn, LTR_CRATE_SERIAL_SIZE - 1);
        snd_data[LTR_CRATE_SERIAL_SIZE-1] = '\0';
    } else {
        memset(snd_data, 0, LTR_CRATE_SERIAL_SIZE);
    }

    snd_data[LTR_CRATE_SERIAL_SIZE] = crate_iface;
    snd_data[LTR_CRATE_SERIAL_SIZE+1] = 0;
    snd_data[LTR_CRATE_SERIAL_SIZE+2] = slot & 0xFF;
    snd_data[LTR_CRATE_SERIAL_SIZE+3] = (slot >> 8) & 0xFF;
    return f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_GET_MODULE_STAT, sizeof(snd_data), size, snd_data,
        stat, NULL, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetServerParameter(TLTR *hsrv, DWORD param, void *val, DWORD *size) {
    char snd_data[4];
    INT err = (size == NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        snd_data[0] = param & 0xFF;
        snd_data[1] = (param >> 8) & 0xFF;
        snd_data[2] = (param >> 16) & 0xFF;
        snd_data[3] = (param >> 24) & 0xFF;

        err = f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_GET_SERVER_PARAM, 4, *size, snd_data, val, size,
            NULL);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetServerProcessPriority(TLTR *ltr, DWORD *Priority) {
    /* Чтение приоритета процесса ltrserver.exe */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_SERVER_PROCESS_PRIORITY};
    return f_StdCtlFuncDef(ltr, sbuf, sizeof(sbuf), Priority, sizeof(DWORD), NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_GetServerVersion(TLTR *hsrv, DWORD *version) {
    /* Чтение номера версии сервера (DWORD, 0x01020304 = 1.2.3.4) */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_SERVER_VERSION};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), version, sizeof(DWORD), NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Init(TLTR *ltr) {
    if (!ltr)
        return LTR_ERROR_PARAMETERS;

    ltr->saddr = LTRD_ADDR_DEFAULT;
    ltr->sport = LTRD_PORT_DEFAULT;
    ltr->csn[0] = '\0';
    ltr->cc = LTR_CC_CHNUM_CONTROL;
    ltr->flags = 0;
    ltr->tmark = 0xFFFFFFFF;
    ltr->Internal = NULL;
    return LTR_OK;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_IsOpened(TLTR *ltr) {    
    return LTR_CHECK_OPENED(ltr);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_MakeStartMark(TLTR *hcrate, INT mode) {
    /* Подача метки СТАРТ (однократно) */
    WORD reg_val = mode;
    return f_SetFPGAConfigRegisters(hcrate, 0x0E, sizeof(reg_val), &reg_val);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Open(TLTR *ltr) {
    return LTR_OpenEx(ltr, 0);
}

LTRAPIWIN_DllExport(INT) LTR_OpenCrate(TLTR *hcrate, DWORD ltrd_addr, WORD ltrd_port, INT crate_iface, const char *crate_sn) {
    INT err = hcrate == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {

        hcrate->cc = LTR_CC_CHNUM_CONTROL;
        if (crate_iface == LTR_CRATE_IFACE_USB) {
            hcrate->cc |= LTR_CC_IFACE_USB;
        } else if (crate_iface == LTR_CRATE_IFACE_TCPIP) {
            hcrate->cc |= LTR_CC_IFACE_ETH;
        } else if (crate_iface != LTR_CRATE_IFACE_UNKNOWN) {
            err = LTR_ERROR_PARAMETERS;
        }
    }

    if (err == LTR_OK) {
        if (crate_sn != NULL) {
            strncpy(hcrate->csn, crate_sn, LTR_CRATE_SERIAL_SIZE - 1);
            hcrate->csn[LTR_CRATE_SERIAL_SIZE - 1] = '\0';
        } else {
            memset(hcrate->csn, 0, LTR_CRATE_SERIAL_SIZE);
        }

        hcrate->saddr = ltrd_addr;
        hcrate->sport = ltrd_port;
        err = LTR_Open(hcrate);
    }

    return err;
}

LTRAPIWIN_DllExport(INT) LTR_OpenSvcControl(TLTR *hsrv, DWORD ltrd_addr, WORD ltrd_port) {
    return LTR_OpenCrate(hsrv, ltrd_addr, ltrd_port, LTR_CRATE_IFACE_UNKNOWN, LTR_CSN_SERVER_CONTROL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_OpenEx(TLTR *ltr, DWORD timeout) {
    DWORD ack = 0;
    TLTR_CHANNEL* ch = NULL;
    INT res = LTR_OK;
    t_ltimer tmr;

    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(timeout == 0 ? OPEN_RETRY_TIMEOUT : timeout));

    res = LTR_Close(ltr);
    if (res == LTR_OK) {
        /* создаем структуру с приватными данными канала с сервером */
        ch = ltr_ch_create();
        if (ch == NULL)
           res = LTR_ERROR_MEMORY_ALLOC;
    }

    if (res == LTR_OK) {
        /* Открываем соединение с сервером */
        res = ltr_ch_open(ch, ltr->saddr, ltr->sport, &tmr);
    }

    if (res == LTR_OK) {
        struct {
            DWORD cmd_sign;
            DWORD cmd;
            char csn[LTR_CRATE_SERIAL_SIZE];
            WORD ch;
            WORD mark_l;
            WORD mark_h;
        } cmd;

        memset(&cmd, 0, LTR_INIT_CMD_SIZE);
        cmd.cmd_sign = CONTROL_COMMAND_START;
        cmd.cmd = CONTROL_COMMAND_INIT_CONNECTION;
        strncpy(cmd.csn, ltr->csn, LTR_CRATE_SERIAL_SIZE);
        cmd.csn[LTR_CRATE_SERIAL_SIZE - 1] = '\0';
        cmd.ch = ltr->cc;

        /* Посылаем команду инициализации соединения, аналогично f_StdCtlFunc,
         * но команду инициализации можно посылать не только по контрольному каналу +
         * ack=CONTROL_ACKNOWLEDGE_MODULE_IN_USE не приводит к ошибке
         */
        if ((ltr_ch_send(ch, (LPCBYTE)&cmd, LTR_INIT_CMD_SIZE, &tmr) != LTR_INIT_CMD_SIZE)
            || (ltr_ch_recv(ch, (LPBYTE)&ack, sizeof(ack), &tmr) != (INT)sizeof(ack))) {
            res = LTR_ERROR_OPEN_CHANNEL;
        } else {
            res = f_get_cmd_ack_err(ack);
            if (res == LTR_WARNING_MODULE_IN_USE)
                res = LTR_OK;

            if (res == LTR_OK) {
                if (ltr_ch_recv(ch, (LPBYTE)&cmd, LTR_INIT_CMD_SIZE, &tmr) != LTR_INIT_CMD_SIZE)
                    res = LTR_ERROR_OPEN_CHANNEL;
            }
        }

        if (res == LTR_OK) {
            /* В полученном отклике сервер заполняет секундные метки и
             * серийный номер открытого крейта
             */
            ltr->tmark = cmd.mark_l + ((DWORD)cmd.mark_h << 16) ;
            /* Если серийный номер не был задан - берем из ответа */
            if (!ltr->csn[0]) {
                memcpy(ltr->csn, cmd.csn, LTR_CRATE_SERIAL_SIZE);
            } else if (strncmp(ltr->csn, cmd.csn, LTR_CRATE_SERIAL_SIZE)) {
                /* если был задан, но не совпадает с ответом - ошибка */
                res = LTR_ERROR_OPEN_CHANNEL;
            }
        }
    }


    if (res == LTR_OK) {
        /* Если связь создана успешно - сохраняем созданную структуру канала */
        ltr->Internal = (LPVOID)ch;
    } else if (ch != NULL) {
        /* иначе - освобождаем память */
        ltr_ch_free(ch);
    }

    /* если канал был открыт успешно, но этот канал уже используется - возвращаем предупреждение */
    if ((res == LTR_OK) && (ack == CONTROL_ACKNOWLEDGE_MODULE_IN_USE))
        res = LTR_WARNING_MODULE_IN_USE;

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_PrivateBufAlloc(TLTR *ltr, DWORD id, DWORD size, DWORD flags) {
    INT err = LTR_CHECK_OPENED(ltr);
    if (err==LTR_OK) {
        err = ltr_ch_priv_alloc(LTR_CHANNEL(ltr), id, size);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_PrivateBufFree(TLTR *ltr, DWORD id) {
    INT err = LTR_CHECK_OPENED(ltr);
    if (err==LTR_OK) {
        err = ltr_ch_priv_free(LTR_CHANNEL(ltr), id);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(LPVOID) LTR_PrivateBufGet(TLTR *ltr, DWORD id) {
    return LTR_CHECK_OPENED(ltr) != LTR_OK ? NULL : ltr_ch_priv_get(LTR_CHANNEL(ltr), id);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_PutSettings(TLTR *hcrate, const TLTR_SETTINGS *settings) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    INT ret_code = LTR_OK;
    if ((hcrate == NULL) || (settings == NULL))
        ret_code = LTR_ERROR_PARAMETERS;

    if (ret_code == LTR_OK)
        ret_code = LTR_IsOpened(hcrate);

    if (ret_code == LTR_OK) {
        if (LTR_CC_CHNUM(hcrate->cc) != LTR_CC_CHNUM_CONTROL)
            ret_code = LTR_ERROR_NOT_CTRL_CHANNEL;
    }

    if (ret_code == LTR_OK) {
        int slots_supported;
        ret_code = ltrslot_check_support_ltr(hcrate, &slots_supported);
        if ((ret_code == LTR_OK) && !slots_supported)
            ret_code = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (ret_code == LTR_OK) {
        if (settings->size > sizeof(TLTR_SETTINGS))
            ret_code = LTR_ERROR_PARAMETERS;
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_write(hcrate, LTRSLOT_NCONTROLLER_SLOT, 0, settings->size, settings);

    if (ret_code == LTR_OK)
        ret_code = ltrslot_save_slot_settings(hcrate, LTRSLOT_NCONTROLLER_SLOT);

    return ret_code;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_RawRecv(TLTR *ltr, BYTE *buf, DWORD size, DWORD tout) {
    INT err = LTR_CHECK_OPENED(ltr);
    if ((err==LTR_OK) && ((buf == NULL) || (size == 0)))
        err = LTR_ERROR_PARAMETERS;
    if (err==LTR_OK) {
        t_ltimer tmr;
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR_TIMEOUT(ltr, tout)));
        err = ltr_ch_recv(LTR_CHANNEL(ltr), buf, size, &tmr);
    }
    return err;
}

LTRAPIWIN_DllExport(INT) LTR_SendShutdown(TLTR *ltr) {
    INT err = LTR_CHECK_OPENED(ltr);
    if (err==LTR_OK)
       ltr_ch_tx_shutdown(LTR_CHANNEL(ltr));
    return err;
}

static INT f_recv_dwords(TLTR_CHANNEL *ch, DWORD *data, DWORD size, t_ltimer *ptmr) {
    INT recvd;
    if (ch->recv_part_size != 0) {
        data[0] = ch->recv_part_wrd;
    }
    recvd = ltr_ch_recv(ch, (LPBYTE)data + ch->recv_part_size,
                        size *sizeof(data[0]) - ch->recv_part_size, ptmr);
    if (recvd > 0) {
        recvd += ch->recv_part_size;
        ch->recv_part_size = recvd % sizeof(data[0]);
        recvd /= sizeof(data[0]);
        if (ch->recv_part_size!=0) {
            ch->recv_part_wrd = data[recvd];
        }
    }
    return recvd;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Recv(TLTR *hmodule, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    return LTR_RecvEx(hmodule, data, tmark, size, timeout, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_RecvEx(TLTR *hmodule, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout,
                                    LONGLONG *unixtime) {

    INT err = LTR_CHECK_OPENED(hmodule);
    INT num_recv = 0;

    if ((err == LTR_OK) && ((data == NULL) || (size == 0)))
        err = LTR_ERROR_PARAMETERS;
    if (err == LTR_OK) {
        TLTR_CHANNEL *channel = LTR_CHANNEL(hmodule);
        t_ltimer tmr;
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR_TIMEOUT(hmodule, timeout)));

        hmodule->flags &= ~LTR_FLAG_RBUF_OVF;

        if (LTR_CC_CHNUM(hmodule->cc) == LTR_CC_CHNUM_CONTROL) {
            num_recv = f_recv_dwords(channel, data, size, &tmr);
        } else {
            /* Канал данных. Нужно учитывать и пропускать метки времени. */
            do {
                INT xfer_size, i;
                DWORD words_out = 0;

                xfer_size = f_recv_dwords(channel, data, size - num_recv, &tmr);
                if (xfer_size < 0) {
                    err = xfer_size;
                } else {
                    for (i = 0; (i < xfer_size); i++) {
                        DWORD data_word = data[i];
#ifdef LTR_DEBUG_PRINT
                        printf("recv[%2d] = 0x%08x\n", i, data_word);
                        fflush(stdout);
#endif
                        /* находимся внутри esc-последовательности */
                        if (channel->esc.data_count < channel->esc.data_len) {
                            channel->esc.data[channel->esc.data_count++] = data_word;

                            /* конец последовательности */
                            if (channel->esc.data_count >= channel->esc.data_len) {
                                switch (channel->esc.type) {
                                    case LTR_SIG_TYPE_LEGACY: /* TSTAMP (RBUF_OVF не имеет данных) */
                                        hmodule->tmark = channel->esc.data[0];
                                        break;
                                    case LTR_SIG_TYPE_TSTAMP_EX:
                                        hmodule->tmark = channel->esc.data[0];
                                        if (channel->esc.data_len > 2) {
                                            channel->unixtime.LowPart = channel->esc.data[1];
                                            channel->unixtime.HighPart = channel->esc.data[2];
                                        }
                                        break;
                                }
                            }
                        } else if (LTR_SIG_ESC_VAL == (data_word & LTR_SIG_ESC_MASK)) {
                            /* начало esc-последовательности */
                            channel->esc.type = data_word & LTR_SIG_TYPE_MASK;
                            channel->esc.data_count = 0;
                            channel->esc.data_len = LTR_SIG_LEN(data_word);
                            if (channel->esc.data_len > LTR_SIG_MAX_DWORDS)
                                channel->esc.data_len = LTR_SIG_MAX_DWORDS;

                            if (!channel->esc.data_len) {
                                /* esc-последовательность без данных обрабатыавется сразу */
                                switch (channel->esc.type) {
                                    case LTR_SIG_TYPE_LEGACY: /* RBUF_OVF (размер данных 0) */
                                        hmodule->flags |= LTR_FLAG_RBUF_OVF;
                                        break;
                                }
                            }
                        } else {
                            /* нормальные данные */
                            if (tmark)
                                tmark[words_out] = hmodule->tmark;
                            if (unixtime)
                                unixtime[words_out] = channel->unixtime.QuadPart;
                            data[words_out++] = data_word;
                        }
                    } /* for i */

                    num_recv += words_out;
                    data += words_out;
                    if (tmark)
                        tmark += words_out;
                }
            } while ((num_recv < (INT)size) && !ltimer_expired(&tmr) && (err == LTR_OK));
        } /*else @if ((ltr->cc & (~CC_DEBUG_FLAG)) == CC_CONTROL)@ */
    }

    return err == LTR_OK ? num_recv : err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_ResetModule(TLTR *hsrv, INT crate_iface, const char *crate_sn,
                                         INT module_slot, DWORD flags) {
    char snd_data[LTR_CRATE_SERIAL_SIZE+8];
    if (crate_sn != NULL) {
        strncpy(snd_data, crate_sn, LTR_CRATE_SERIAL_SIZE - 1);
        snd_data[LTR_CRATE_SERIAL_SIZE-1] = '\0';
    } else {
        memset(snd_data, 0, LTR_CRATE_SERIAL_SIZE);
    }

    snd_data[LTR_CRATE_SERIAL_SIZE] = crate_iface;
    snd_data[LTR_CRATE_SERIAL_SIZE+1] = 0;
    snd_data[LTR_CRATE_SERIAL_SIZE+2] = module_slot & 0xFF;
    snd_data[LTR_CRATE_SERIAL_SIZE+3] = (module_slot >> 8) & 0xFF;

    snd_data[LTR_CRATE_SERIAL_SIZE+4] = flags & 0xFF;
    snd_data[LTR_CRATE_SERIAL_SIZE+5] = (flags >> 8) & 0xFF;
    snd_data[LTR_CRATE_SERIAL_SIZE+6] = (flags >> 16) & 0xFF;
    snd_data[LTR_CRATE_SERIAL_SIZE+7] = (flags >> 24) & 0xFF;

    return f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_RESET_MODULE, sizeof(snd_data), 0, snd_data, NULL,
        NULL, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_Send(TLTR *hmodule, const DWORD *data, DWORD size, DWORD timeout) {
#ifdef LTR_DEBUG_PRINT
    DWORD i;
#endif
    INT err = LTR_CHECK_OPENED(hmodule);
    INT xfer_size = 0;
    if ((err == LTR_OK) && ((data == NULL) || (size == 0)))
        err = LTR_ERROR_PARAMETERS;
    if (err == LTR_OK) {
        TLTR_CHANNEL *channel = LTR_CHANNEL(hmodule);
        t_ltimer tmr;
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR_TIMEOUT(hmodule, timeout)));

#ifdef LTR_DEBUG_PRINT
        for (i = 0; i < size; i++)
            printf( "send[%2i] = 0x%08x\n", i, data[i]);
        fflush(stdout);
#endif

        /* проверяем, не осталось ли не переданного некратного слова => если осталось
         * то пробуем сперва дослать его */
        if (channel->send_part_size!=0) {
            xfer_size = ltr_ch_send(channel, (LPCBYTE)&channel->send_part_wrd,
                                    channel->send_part_size, &tmr);
            if (xfer_size < 0) {
                err = xfer_size;
            } else {
                channel->send_part_size -= xfer_size;
                if (channel->send_part_size != 0) {
                    channel->send_part_wrd >>= 8*xfer_size;
                }
            }
        }

        if (err==LTR_OK) {
            /* новые данные пересылаем только если старое неполное слово точно ушло */
            if (channel->send_part_size==0) {
                xfer_size = ltr_ch_send(channel, (LPCBYTE)data, size * sizeof(DWORD), &tmr);
                if (xfer_size < 0) {
                    err = xfer_size;
                } else {
                    /* если не полностью передали последнее слово, то нужно сохранить
                     * остаток слова, чтобы потом передать его */
                    channel->send_part_size = xfer_size % sizeof(data[0]);
                    xfer_size /= sizeof(data[0]);
                    if (channel->send_part_size!=0) {
                        channel->send_part_wrd = data[xfer_size] >> (8*channel->send_part_size);
                        channel->send_part_size = sizeof(data[0]) - channel->send_part_size;
                        xfer_size++;
                    }
                }
            } else {
                xfer_size = 0;
            }
        }
    }

    return err == LTR_OK ? xfer_size : err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_ServerRestart(TLTR *hsrv) {
    /* Рестарт программы ltrserver (с разрывом всех соединений).
     * После успешного выполнения команды связь с сервером теряется.
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_RESTART_SERVER};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_ServerShutdown(TLTR *hsrv) {
    /* Рестарт программы ltrserver (с разрывом всех соединений)
     * После успешного выполнения команды связь с сервером теряется.
     */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_SHUTDOWN_SERVER};
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetIPCrateDiscoveryMode(TLTR *ltr, BOOL enabled, BOOL autoconnect,
    BOOL permanent) {
    /* Установка режима поиска IP-крейтов в локальной сети */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_SET_DISCOVERY_MODE, 0, 0, 0};

    sbuf[2] = enabled;
    sbuf[3] = autoconnect;
    sbuf[4] = permanent;
    return f_StdCtlFuncDef(ltr, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetIPCrateFlags(TLTR *hsrv, DWORD ip_addr, DWORD flags,
    BOOL permanent) {
    /* Изменение флагов для IP-крейта */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_IP_SET_FLAGS, 0, 0, 0};
    sbuf[2] = ip_addr;
    sbuf[3] = flags;
    sbuf[4] = permanent;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetLogLevel(TLTR *hsrv, INT level, BOOL permanent) {
    /* Установка уровня журнализации */
    DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_SET_LOG_LEVEL, 0, 0};
    sbuf[2] = level;
    sbuf[3] = permanent;
    return f_StdCtlFuncDef(hsrv, sbuf, sizeof(sbuf), NULL, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetServerParameter(TLTR *hsrv, DWORD param, void *val, DWORD size) {
    INT err = LTR_OK;
    char *snd_data = malloc(size + 4);
    if (snd_data == NULL) {
        err = LTR_ERROR_MEMORY_ALLOC;
    } else {
        snd_data[0] = param & 0xFF;
        snd_data[1] = (param >> 8) & 0xFF;
        snd_data[2] = (param >> 16) & 0xFF;
        snd_data[3] = (param >> 24) & 0xFF;
        memcpy(&snd_data[4], val, size);

        err = f_StdCtlFuncEx(hsrv, LTR_CLIENT_CMD_SET_SERVER_PARAM, size+4, 0, snd_data, NULL, NULL,
            NULL);

        free(snd_data);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetServerProcessPriority(TLTR *ltr, DWORD Priority) {
    /* Функция исторически реализована не так, как остальные, с двумя посылками и двумя ACK. */   
    INT err = LTR_CHECK_OPENED(ltr);
    if (err == LTR_OK) {
        DWORD sbuf[] = {CONTROL_COMMAND_START, CONTROL_COMMAND_SET_SERVER_PROCESS_PRIORITY};
        t_ltimer tmr;
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR_TIMEOUT(ltr, 0)));
        err = f_StdCtlFuncDef(ltr, sbuf, sizeof(sbuf), NULL, 0, &tmr);
        if (err == LTR_OK)
            err = f_StdCtlFuncDef(ltr, &Priority, sizeof(Priority), NULL, 0, &tmr);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_SetTimeout(TLTR *hnd, DWORD tout) {
    /* Установка таймаута по умолчанию для send/recv
     * Используется в LTR_Send и LTR_Recv, если задан таймаут 0,
     * а также во всех командах управляющего канала.
     */
    INT err = LTR_CHECK_OPENED(hnd);
    if (err == LTR_OK) {
        ltr_ch_set_def_tout(LTR_CHANNEL(hnd), tout);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_StartSecondMark(TLTR *hcrate, INT mode) {
    /* Включение внутренней генерации секундных меток */
    WORD reg_val = mode;
    if (mode == LTR_MARK_INTERNAL)
        reg_val++;
    return f_SetFPGAConfigRegisters(hcrate, 0x0F, sizeof(reg_val), &reg_val);
}

/*------------------------------------------------------------------------------------------------*/
LTRAPIWIN_DllExport(INT) LTR_StopSecondMark(TLTR *hcrate) {
    /* Отключение внутренней генерации секундных меток */
    WORD reg_val = 0x00;
    return f_SetFPGAConfigRegisters(hcrate, 0x0F, sizeof(reg_val), &reg_val);
}


// Чтение последней принятой и обработанной метки unixtime (0, если таких меток не было)
LTRAPIWIN_DllExport(INT) LTR_GetLastUnixTimeMark(TLTR *hmodule, LONGLONG* unixtime) {

    INT err = LTR_CHECK_OPENED(hmodule);
    if ((err == LTR_OK) && (unixtime==NULL))
        err = LTR_ERROR_PARAMETERS;
    if (err == LTR_OK) {
        TLTR_CHANNEL *ch = LTR_CHANNEL(hmodule);
        *unixtime = ch->unixtime.QuadPart;
    }
    return err;
}


LTRAPIWIN_DllExport(INT) LTR_CrateGetArray(TLTR *hcrate, DWORD address, BYTE *buf, DWORD size) {
    DWORD sbuf[4];
    sbuf[0] = CONTROL_COMMAND_START;
    sbuf[1] = CONTROL_COMMAND_GET_ARRAY;
    sbuf[2] = address;
    sbuf[3] = size;
    return f_StdCtlFuncDef(hcrate, sbuf, sizeof(sbuf), buf, size, NULL);
}

LTRAPIWIN_DllExport(INT) LTR_CratePutArray(TLTR *hcrate, DWORD address, const BYTE *buf, DWORD size) {
    INT err = LTR_OK;
    DWORD send_size = 4 * sizeof(DWORD) + size;
    LPDWORD pbuf = (LPDWORD)malloc(send_size);
    if (pbuf==NULL) {
        err = LTR_ERROR_MEMORY_ALLOC;
    } else {
        pbuf[0] = CONTROL_COMMAND_START;
        pbuf[1] = CONTROL_COMMAND_PUT_ARRAY;
        pbuf[2] = address;
        pbuf[3] = size;
        memcpy(&pbuf[4], buf, size);
        err = f_StdCtlFuncDef(hcrate, pbuf, send_size, NULL, 0, NULL);
        free(pbuf);
    }
    return err;
}

LTRAPIWIN_DllExport(INT) LTR_ModuleCustomSendWithEcho(TLTR *hmodule, const DWORD *data, DWORD size, DWORD *ack) {
    return ltr_module_send_with_echo_resps(hmodule, data, size, ack);
}


