#include "ltrsrv_usb.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_crates.h"
#include "ltrsrv_log.h"
#include "ltrsrv_usb_p.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_opt_parse.h"

#include <Windows.h>
#include <winioctl.h>
#include <SetupAPI.h>
#include <initguid.h>
#include <tchar.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>

#ifdef LTRD_DAEMON_ENABLED
    #include <Dbt.h>
#endif



#define DIOC_SEND_COMMAND \
        CTL_CODE(FILE_DEVICE_UNKNOWN, 15, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)


typedef struct {
    HANDLE hDevice;
    OVERLAPPED ctl_ovrlp;
    BOOLEAN canceled;
} t_usb_lcomp_data;


static const CLSID USB_DEVICE_CLSID = {
    0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED }
    };


static int f_open(t_usb_data *intf);
static int  f_start_ioctl(t_usb_data* intf, uint8_t cmd, uint32_t param,
                          uint16_t reply_len, uint16_t data_len,
                          const uint8_t* tx_data);
static int f_start_bulk(t_usb_data* intf, t_bulk_transf_ctl* bulk_ctl, unsigned timeout);
static void f_transf_cancel(t_usb_data *intf, t_transf_params *transf);
static void f_cleanup(t_usb_data *intf);

static int f_devid_cmp(const void *dev1, const void *dev2);
static void f_check_devlist(void);

FILE* f_fopen_spec(const char* fname, const char* mode);


static const t_usb_port_intf f_port_intf = {
    f_open,
    f_start_ioctl,
    f_start_bulk,
    f_transf_cancel,
    f_cleanup,
    f_fopen_spec,

    f_devid_cmp,
    free
};



unsigned f_crate_str_cnt;
static struct {
    t_ltr_crate_type type;
    TCHAR str[18];
} *f_crates_str;



#ifdef LTRD_DAEMON_ENABLED
#define LTRD_DEVNOTIFY_ARRAY_SIZE            128

static HDEVNOTIFY f_devnotify_hnd;
extern SERVICE_STATUS_HANDLE service_hnd;
static HANDLE f_devnotify_event;

typedef enum {
    LTRD_DEVNOTIFY_TYPE_NONE,
    LTRD_DEVNOTIFY_TYPE_ARRIVED,
    LTRD_DEVNOTIFY_TYPE_REMOVED
} t_ltrd_devnotify_type;

typedef struct {
    t_ltrd_devnotify_type type;
    SP_DEVICE_INTERFACE_DETAIL_DATA *iface_detail;
} t_ltrd_devnotify_info;



static unsigned f_devnotify_put_pos=0;
static unsigned f_devnotify_get_pos=0;
static volatile t_ltrd_devnotify_info f_devnotifies[LTRD_DEVNOTIFY_ARRAY_SIZE];

static int f_devnotify_init(void);
static void f_devnotify_close(void);
#endif



static t_ltr_crate_type f_devpath_get_crate_type(TCHAR* devpath) {
    size_t i;
    for (i=0; i < f_crate_str_cnt; i++) {
        if (_tcsstr(devpath, f_crates_str[i].str) != NULL)
            return f_crates_str[i].type;
    }
    return LTR_CRATE_TYPE_UNKNOWN;
}


static int f_devid_cmp(const void *dev1, const void *dev2) {
    return !_tcscmp(((SP_DEVICE_INTERFACE_DETAIL_DATA*)dev1)->DevicePath,
                     ((SP_DEVICE_INTERFACE_DETAIL_DATA*)dev2)->DevicePath);
}



FILE* f_fopen_spec(const char* fname, const char* mode) {
    FILE *ret = 0;
    /* заменяем относительный путь . на путь относительно
     * исполняемого файла, чтобы не зависеть от директории из которой
     * вызван ltrd */
    if (fname[0]=='.') {
        static TCHAR fileName[MAX_PATH];
        static TCHAR modeStr[20];
        fname++;

        /* получаем полное имя исполняемого файла (с путем) */
        if ( GetModuleFileName(NULL, fileName, MAX_PATH ) ) {
            int last_pos=0, pos;

            /* получаем только путь, удаляя имя файла */
            for (pos = 0; (pos < MAX_PATH) && (fileName[pos]!='\0'); pos++) {
                if (fileName[pos]==TEXT('\\'))
                    last_pos = pos;
            }


            while (*fname!='\0') {
                fileName[last_pos++] = (TCHAR)(*fname);
                fname++;
            }

            fileName[last_pos] = '\0';

            for (pos=0; (pos < (sizeof(modeStr)/sizeof(modeStr[0])-1))
                 && (*mode!='\0'); pos++) {
                modeStr[pos] = (TCHAR)(*mode);
                mode++;
            }
            modeStr[pos] = '\0';

            ret = _tfopen(fileName, modeStr);
        }
    } else {
        ret = fopen(fname, mode);
    }
    return ret;
}




static void f_ioctl_cb(t_socket sock, int event, void* data) {
    int trans_err = 0;
    int proc = 0;
    DWORD len = 0;
    t_usb_data *intf = (t_usb_data *)data;
    t_usb_lcomp_data *privdata = (t_usb_lcomp_data *)intf->privdata;

    if (event & LTRSRV_EVENT_TOUT) {
        CancelIo(privdata->hDevice);
        GetOverlappedResult(privdata->hDevice, &privdata->ctl_ovrlp, &len, TRUE);
        CloseHandle(privdata->ctl_ovrlp.hEvent);
        privdata->ctl_ovrlp.hEvent = INVALID_HANDLE_VALUE;
        proc = 1;
        trans_err = LTRSRV_ERR_USB_TRANSF_TOUT;
    } else if (event & LTRSRV_EVENT_RAW_EVT) {
        proc = 1;
        if(!GetOverlappedResult(privdata->hDevice, &privdata->ctl_ovrlp, &len, TRUE)) {
            CancelIo(privdata->hDevice);
            trans_err = LTRSRV_ERR_USB_IOCTL_FAILD;
        }
        CloseHandle(privdata->ctl_ovrlp.hEvent);
        privdata->ctl_ovrlp.hEvent = INVALID_HANDLE_VALUE;
    }

    if (proc) {
        p_usb_process_ioctl(intf, trans_err, intf->ctl.buf, len);
    }
}

static void f_bulk_cb(t_socket sock, int event, void* data) {
    int transf_err = 0;
    int proc = 0;
    DWORD len = 0;
    t_usb_data *intf = (t_usb_data *)data;
    t_usb_lcomp_data *privdata = (t_usb_lcomp_data *)intf->privdata;
    t_transf_params *transf;
    OVERLAPPED *pOv;
    int syserr = 0;


    transf = &intf->bulk_rd.transfs[intf->bulk_rd.get_pos];
    pOv = (OVERLAPPED*) (transf->port_transfer);

    if ((pOv!=NULL) && ((HANDLE)sock==pOv->hEvent)) {
        if (event & LTRSRV_EVENT_TOUT) {
            if (!privdata->canceled)
                CancelIo(privdata->hDevice);
            GetOverlappedResult(privdata->hDevice, pOv, &len, TRUE);
            proc = 1;
            transf_err = LTRSRV_ERR_USB_TRANSF_TOUT;
        } else if (event & LTRSRV_EVENT_RAW_EVT) {
            proc = 1;            
            if(!GetOverlappedResult(privdata->hDevice, pOv, &len, TRUE)) {
                syserr = LTRSRV_LAST_SYSERR;
                if (!privdata->canceled) {
                    if (syserr != ERROR_OPERATION_ABORTED)
                        transf_err = LTRSRV_ERR_USB_TRANSFER_FAILED;
                }
            }
        }

        CloseHandle(pOv->hEvent);
        pOv->hEvent = INVALID_HANDLE_VALUE;

        if (proc) {
            p_usb_process_bulk_rd(intf, transf_err, syserr, transf->buf, len);
        }
    } else {
        transf = &intf->bulk_wr.transfs[intf->bulk_wr.get_pos];
        pOv = (OVERLAPPED*) (transf->port_transfer);

        if ((pOv!=NULL) && ((HANDLE)sock==pOv->hEvent)) {
            if (event & LTRSRV_EVENT_TOUT) {
                if (!privdata->canceled)
                    CancelIo(privdata->hDevice);
                GetOverlappedResult(privdata->hDevice, pOv, &len, TRUE);
                proc = 1;
                transf_err = LTRSRV_ERR_USB_TRANSF_TOUT;
            } else if (event & LTRSRV_EVENT_RAW_EVT) {
                proc = 1;
                if(!GetOverlappedResult(privdata->hDevice, pOv, &len, TRUE)) {
                    syserr = LTRSRV_LAST_SYSERR;
                    if (!privdata->canceled) {
                        CancelIo(privdata->hDevice);
                        if (syserr != ERROR_OPERATION_ABORTED)
                            transf_err = LTRSRV_ERR_USB_TRANSFER_FAILED;
                    }
                }
            }
            CloseHandle(pOv->hEvent);
            pOv->hEvent = INVALID_HANDLE_VALUE;;

            if (proc) {
                p_usb_process_bulk_wr(intf, transf_err, syserr, transf->buf, len);
            }
        } else {

            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR_FATAL,
                           intf->crate->log_str, LTRSRV_ERR_USB_TRANSF_OUT_OF_ORDER,
                           "Invalid transfer completed!");
        }
    }
}

static int f_start_ioctl(t_usb_data* intf, uint8_t cmd, uint32_t param,
                         uint16_t reply_len, uint16_t data_len, const uint8_t* tx_data) {
    t_usb_lcomp_data *privdata = (t_usb_lcomp_data *)intf->privdata;
    uint16_t usbreq[4];
    int err = 0;
    uint16_t len;
    DWORD bytes_ret;

    if (reply_len) {
        usbreq[0] = 1;
        len = reply_len;
    } else {
        usbreq[0] = 0;
        len = data_len;
    }

    /* если выделенный буфер меньше заданного размера - выделяем новый */
    if (intf->ctl.len < len) {
        free(intf->ctl.buf);
        intf->ctl.buf = (uint8_t*)malloc(len);
        if (intf->ctl.buf!=NULL) {
            intf->ctl.len = len;
        } else {
            intf->ctl.len = 0;
            err = LTRSRV_ERR_MEMALLOC;
        }
    }

    if (!err && data_len) {
        memmove(intf->ctl.buf, tx_data, data_len);
    }

    if (!err) {
        //заполняем последующие 3 слова запроса к драйверу
        usbreq[1] = cmd;
        usbreq[2] = param & 0xFFFF;
        usbreq[3] = (param >> 16) & 0xFFFF;

        //ResetEvent(privdata->ctl_ovrlp.hEvent);
        memset(&privdata->ctl_ovrlp, 0, sizeof(OVERLAPPED));
        privdata->ctl_ovrlp.hEvent =
                CreateEvent(NULL, TRUE, FALSE, NULL);
        if (!DeviceIoControl(privdata->hDevice, DIOC_SEND_COMMAND,
                             usbreq, sizeof(usbreq),
                             intf->ctl.buf, len,
                             &bytes_ret, &privdata->ctl_ovrlp)) {
            DWORD err_code = GetLastError();
            if(err_code != ERROR_IO_PENDING) {
                err =  LTRSRV_ERR_USB_IOCTL_FAILD;
                GetOverlappedResult(privdata->hDevice, &privdata->ctl_ovrlp,
                                    &bytes_ret, TRUE);
                CloseHandle(privdata->ctl_ovrlp.hEvent);
            }
        }
    }

    if (!err) {
        err = ltrsrv_wait_add_evt(privdata->ctl_ovrlp.hEvent,
                                  LTRSRV_WAITFLG_TOUT | LTRSRV_WAITFLG_AUTODEL,
                                  LTRSRV_USB_CTL_TOUT, f_ioctl_cb, intf);
        if (err) {
            GetOverlappedResult(privdata->hDevice, &privdata->ctl_ovrlp,
                                &bytes_ret, TRUE);
            CloseHandle(privdata->ctl_ovrlp.hEvent);
        }
    }

    if (!err)
        intf->ctl.state = USB_TRANSF_STATE_PROGRESS;
    return err;
}


static int f_start_bulk(t_usb_data* intf, t_bulk_transf_ctl* bulk_ctl, unsigned timeout) {
    int err = bulk_ctl->transfs[bulk_ctl->put_pos].state == USB_TRANSF_STATE_FREE ?
               0 : LTRSRV_ERR_USB_TRANSF_BUSY;
    t_transf_params* trans;


    if (!err) {
        trans = &bulk_ctl->transfs[bulk_ctl->put_pos];
        if (trans->port_transfer==NULL) {
            OVERLAPPED *port_transf = calloc(1, sizeof(OVERLAPPED));
            if (port_transf==NULL) {
                err = LTRSRV_ERR_MEMALLOC;
            }

            if (!err)
                trans->port_transfer = port_transf;
        }
    }

    if (!err && (trans->buf==NULL)) {
        trans->buf = (uint8_t*)malloc(LTRSRV_USB_BULK_TRANSF_SIZE);
        if (trans->buf==NULL) {
            err = LTRSRV_ERR_MEMALLOC;
        } else {
            trans->len = LTRSRV_USB_BULK_TRANSF_SIZE;
        }
    }

    if (!err) {
        DWORD bytes_ret;
        t_usb_lcomp_data *privdata = (t_usb_lcomp_data*)(intf->privdata);
        OVERLAPPED *pOv = (OVERLAPPED*)(trans->port_transfer);
        memset(pOv, 0, sizeof(OVERLAPPED));

        pOv->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (pOv->hEvent == INVALID_HANDLE_VALUE) {
            err = LTRSRV_ERR_CREATE_EVENT;
        } else {
            if (bulk_ctl->dir == USB_BULK_DIR_READ) {
                if (!ReadFile(privdata->hDevice, trans->buf, trans->len, NULL, pOv)) {
                    if(GetLastError() != ERROR_IO_PENDING) {                        
                        err =  LTRSRV_ERR_USB_START_TRANSFER;
                        GetOverlappedResult(privdata->hDevice, pOv,
                                            &bytes_ret, TRUE);
                        CloseHandle(pOv->hEvent);
                        pOv->hEvent = INVALID_HANDLE_VALUE;
                    }
                }
            } else {
                if (!WriteFile(privdata->hDevice, trans->buf, trans->len, NULL, pOv)) {
                    if(GetLastError() != ERROR_IO_PENDING) {
                        err =  LTRSRV_ERR_USB_START_TRANSFER;
                        GetOverlappedResult(privdata->hDevice, pOv,
                                            &bytes_ret, TRUE);
                        CloseHandle(pOv->hEvent);
                        pOv->hEvent = INVALID_HANDLE_VALUE;
                    }
                }                
            }
        }

        if (!err) {
            err = ltrsrv_wait_add_evt(pOv->hEvent,
                                      (timeout ? LTRSRV_WAITFLG_TOUT : 0) |
                                      LTRSRV_WAITFLG_AUTODEL,
                                      timeout, f_bulk_cb, intf);
            if (err) {
                GetOverlappedResult(privdata->hDevice, pOv,
                                    &bytes_ret, TRUE);
                CloseHandle(pOv->hEvent);
                pOv->hEvent = INVALID_HANDLE_VALUE;
            }
        }

        if (!err) {
            if (++bulk_ctl->put_pos==LTRSRV_USB_BULK_MAX_TRANSF_CNT)
                bulk_ctl->put_pos = 0;
            trans->state = USB_TRANSF_STATE_PROGRESS;
        }
    }

    return err;
}



static void f_transf_cancel(t_usb_data *intf, t_transf_params *transf) {
    t_usb_lcomp_data *privdata = (t_usb_lcomp_data*)intf->privdata;
    if (!privdata->canceled) {
        CancelIo(privdata->hDevice);
        privdata->canceled = 1;
    }
}

static void f_cleanup(t_usb_data *intf) {
    t_usb_lcomp_data *privdata = (t_usb_lcomp_data*)intf->privdata;
    int i;

    for (i=0; i < LTRSRV_USB_BULK_MAX_TRANSF_CNT; i++) {
        free(intf->bulk_rd.transfs[i].port_transfer);
        intf->bulk_rd.transfs[i].port_transfer = NULL;
        free(intf->bulk_wr.transfs[i].port_transfer);
        intf->bulk_wr.transfs[i].port_transfer = NULL;
    }
    CloseHandle(privdata->hDevice);

    p_usb_cleanup_finish(intf);
}



static int f_open(t_usb_data *intf) {
    int err = 0;
    SP_DEVICE_INTERFACE_DETAIL_DATA *detail     = (SP_DEVICE_INTERFACE_DETAIL_DATA *)intf->devid;
    HANDLE hDevice = CreateFile(detail->DevicePath,
                                GENERIC_READ | GENERIC_WRITE, 0,
                                NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        err = LTRSRV_ERR_USB_OPEN_DEVICE;
    } else {
        t_usb_lcomp_data *privdata = calloc(1, sizeof(t_usb_lcomp_data));
        if (privdata==NULL) {
            err = LTRSRV_ERR_MEMALLOC;
        } else {
            privdata->ctl_ovrlp.hEvent =
                    CreateEvent(NULL, TRUE, FALSE, NULL);
            if (privdata->ctl_ovrlp.hEvent==INVALID_HANDLE_VALUE) {
                err = LTRSRV_ERR_CREATE_EVENT;
            } else {
                privdata->hDevice = hDevice;
                intf->privdata = privdata;
            }

            if (err) {
                free(privdata);
            }
        }

        if (err)
            CloseHandle(hDevice);
    }
    return err;
}

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

static void f_check_devlist(void) {
    HDEVINFO infoSet;
    SP_DEVINFO_DATA  infoData;
    DWORD index=0;
    int err = 0;

    p_usb_check_devlist_init();

    infoData.cbSize = sizeof(SP_DEVINFO_DATA);

    /* получаем список устройств с подержкой интерфейса lpcie */
    infoSet = SetupDiGetClassDevs(&USB_DEVICE_CLSID, NULL, NULL,
                                  DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    /* проходимся по всем устройствам из списка */
    while (SetupDiEnumDeviceInfo(infoSet, index, &infoData)) {
        SP_DEVICE_INTERFACE_DATA intfData;
        intfData.cbSize = sizeof(intfData);
        /* получаем информацию о интерфейсе */
        if (SetupDiEnumDeviceInterfaces(infoSet, &infoData, &USB_DEVICE_CLSID,
                                        0, &intfData)) {
            DWORD req_size;
            /* узнаем резмер детальной информации о интерфейсе (нужна для
                получения имени устройства) */
            if (!SetupDiGetDeviceInterfaceDetail(infoSet, &intfData, NULL,
                                                 0, &req_size, NULL)
                    && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
                SP_DEVICE_INTERFACE_DETAIL_DATA* detail =
                        (SP_DEVICE_INTERFACE_DETAIL_DATA*) malloc(req_size);

                if (detail!=NULL) {
                    int detail_used = 0;
                    /* пытаемся получить всю информацию */
                    detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

                    if (SetupDiGetDeviceInterfaceDetail(infoSet, &intfData,
                                                              detail, req_size,
                                                              NULL, NULL)) {
                        if (f_devpath_get_crate_type(detail->DevicePath)!=LTR_CRATE_TYPE_UNKNOWN) {
                            if (p_usb_dev_is_new(detail, &f_port_intf)) {
                                detail->cbSize = req_size;
                                detail_used = 1;
                                p_usb_init_new_dev(detail, &f_port_intf);
                            }
                        }
                    }

                    if (!detail_used)
                        free(detail);
                } else {
                    err = LTRSRV_ERR_MEMALLOC;
                }
            }
        }
        index++;
    }

    if (infoSet)
        SetupDiDestroyDeviceInfoList(infoSet);

    p_usb_check_devlist_finish();
}





int ltrsrv_usb_init(void) {
    int err = LTRSRV_ERR_SUCCESS;

    const t_usb_dev_id *id_entry;

    /* инициализация строк с ID-крейтов */
    for (id_entry = &crate_usb_dev_ids[0], f_crate_str_cnt = 0;
         id_entry->type!=LTR_CRATE_TYPE_UNKNOWN; id_entry++) {

        f_crate_str_cnt++;
    }
    f_crates_str = malloc(sizeof(f_crates_str[0])*f_crate_str_cnt);
    if (f_crates_str == NULL) {
        err = LTRSRV_ERR_MEMALLOC;
    } else {
        unsigned i;
        for (i=0; i < f_crate_str_cnt; i++) {
            f_crates_str[i].type = crate_usb_dev_ids[i].type;
            wsprintf(f_crates_str[i].str, TEXT("vid_%04x&pid_%04x"),
                     crate_usb_dev_ids[i].vid, crate_usb_dev_ids[i].pid);
        }
    }


#ifdef LTRD_DAEMON_ENABLED
    if (err == LTRSRV_ERR_SUCCESS) {
        /* для сервиса регистрируем прием сообщений, чтобы отслеживать
           подключение/отключение устройств */
        if (g_opts.mode == LTRD_MODE_DAEMON) {
            err = f_devnotify_init();
        }
    }
#endif

    if (err == LTRSRV_ERR_SUCCESS) {
        f_check_devlist();

        /* в режиме консоли не можем зарегистрировать событие, поэтому
         * приходится проверять наличие устройств периодическим опросом
         * (возможно создание невидимого окна - но не реализовано) */
        if (g_opts.mode != LTRD_MODE_DAEMON) {
            ltrsrv_wait_add_tmr(&f_port_intf, 0,
                            LTRSRV_USB_DEVCHECK_TOUT,
                            f_update_usb_devs_cb, NULL);
        }
    }

    return err;
}


void ltrsrv_usb_close(void) {
#ifdef LTRD_DAEMON_ENABLED
    if (g_opts.mode == LTRD_MODE_DAEMON) {
        f_devnotify_close();
    }
#endif

    p_usb_close();
    ltrsrv_wait_remove_tmr(&f_port_intf);
    while (f_usb_crates_cnt)
        ltrsrv_wait_event(1000);

    free(f_crates_str);
    f_crates_str = NULL;
}



#ifdef LTRD_DAEMON_ENABLED
    /* Функция основного потока ltrd, вызываемая по событию, что в
     * f_devnotifies появлись новые данные */
    static void f_devnotify_cb(t_socket sock, int event, void* data) {
        /* сбрасываем событие перед обработкой для избежания race-condition */
        ResetEvent(f_devnotify_event);
        /* обрабатываем сообщения из  f_devnotifies */
        while (f_devnotifies[f_devnotify_get_pos].type != LTRD_DEVNOTIFY_TYPE_NONE) {
            volatile t_ltrd_devnotify_info *info = &f_devnotifies[f_devnotify_get_pos];

            int detail_used = 0;
            if (info->type == LTRD_DEVNOTIFY_TYPE_REMOVED) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, LTRSRV_LOGSRC_SERVER, 0, "usb dev removed event");
                p_usb_device_disconnected(info->iface_detail, &f_port_intf);
            } else if (info->type == LTRD_DEVNOTIFY_TYPE_ARRIVED) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, LTRSRV_LOGSRC_SERVER, 0, "usb dev arrived event");
                if (p_usb_dev_is_new(info->iface_detail, &f_port_intf)) {
                    detail_used = 1;
                    p_usb_init_new_dev(info->iface_detail, &f_port_intf);
                }
            }

            /* если информация о устройстве не нужно - освобождаем память */
            if (!detail_used)
                free(info->iface_detail);

            /* освобождаем место в очереди после заверешния обработки */
            info->iface_detail=NULL;
            info->type = LTRD_DEVNOTIFY_TYPE_NONE;


            if (++f_devnotify_get_pos==LTRD_DEVNOTIFY_ARRAY_SIZE)
                f_devnotify_get_pos = 0;

        }
    }

    static int f_devnotify_init(void) {
        DEV_BROADCAST_DEVICEINTERFACE NotifyFilter;
        int i;
        int err = 0;

        /* сбрасываем переменные */
        f_devnotify_get_pos = f_devnotify_put_pos = 0;
        for (i=0; i < LTRD_DEVNOTIFY_ARRAY_SIZE; i++) {
            f_devnotifies[i].type = LTRD_DEVNOTIFY_TYPE_NONE;
            f_devnotifies[i].iface_detail = NULL;
        }

        /* регистрируем приход событий от заданного GUID-интерфейса устройств
         * на сервис */
        ZeroMemory(&NotifyFilter, sizeof(NotifyFilter));
        NotifyFilter.dbcc_size = sizeof(NotifyFilter);
        NotifyFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
        NotifyFilter.dbcc_classguid = USB_DEVICE_CLSID;
        f_devnotify_hnd = RegisterDeviceNotification(service_hnd, &NotifyFilter,
                                                      DEVICE_NOTIFY_SERVICE_HANDLE);


        if (f_devnotify_hnd==NULL) {
            err = LTRSRV_ERR_DEVNOTIFY_REGISTER;
        } else {
            /* создаем событие для индикации из потоков обработки сообщений сервиса
             * основному потоку, что появилась новая информация */
            f_devnotify_event = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (f_devnotify_event == NULL) {
                err = LTRSRV_ERR_RESOURCE_ALLOC;
            } else {
                /* добавляем событие в очередь ожидания */
                err = ltrsrv_wait_add_evt(f_devnotify_event, 0, 0, f_devnotify_cb, NULL);
            }
        }
        return err;
    }

    static void f_devnotify_close(void) {
        if (f_devnotify_hnd != NULL) {
            UnregisterDeviceNotification(f_devnotify_hnd);
            f_devnotify_hnd = NULL;
            if (f_devnotify_event!=NULL) {
                ltrsrv_wait_remove_evt(f_devnotify_event);
                CloseHandle(f_devnotify_event);
                f_devnotify_event = NULL;
                /* освобождаем ресурсы необработанных сообщений из очереди */
                while (f_devnotifies[f_devnotify_get_pos].type != LTRD_DEVNOTIFY_TYPE_NONE) {
                    volatile t_ltrd_devnotify_info *info = &f_devnotifies[f_devnotify_get_pos];
                    free(info->iface_detail);
                    info->iface_detail = NULL;
                    info->type = LTRD_DEVNOTIFY_TYPE_NONE;

                    if (++f_devnotify_get_pos==LTRD_DEVNOTIFY_ARRAY_SIZE)
                        f_devnotify_get_pos = 0;
                }
            }
        }
    }

    /* Функция вызывается из потока обслуживания событий сервиса при изменении
     * состава устройств. Она только проверяет, относится ли событие к крейту
     * и, если да, сохраняет информацию в массиве f_devnotifies для дальнейшего
     * анализа в основном потоке */
    DWORD ltrd_proc_devevent(DWORD dwEventType, LPVOID lpEventData) {
        /* определяем тип события */
        t_ltrd_devnotify_type type;
        if (dwEventType == DBT_DEVICEARRIVAL) {
            type = LTRD_DEVNOTIFY_TYPE_ARRIVED;
        } else if (dwEventType == DBT_DEVICEREMOVECOMPLETE) {
            type = LTRD_DEVNOTIFY_TYPE_REMOVED;
        } else {
            type = LTRD_DEVNOTIFY_TYPE_NONE;
        }

        /* обрабатываем, если это событие поддерживаемого типа и если есть
           место в очереде событий */
        if ((type != LTRD_DEVNOTIFY_TYPE_NONE) &&
                (f_devnotifies[f_devnotify_put_pos].type == LTRD_DEVNOTIFY_TYPE_NONE)) {

            PDEV_BROADCAST_DEVICEINTERFACE pDevIface = (PDEV_BROADCAST_DEVICEINTERFACE)lpEventData;
            if (DBT_DEVTYP_DEVICEINTERFACE == pDevIface->dbcc_devicetype) {
                /* на основе принятой информации создаем структуру SP_DEVICE_INTERFACE_DETAIL_DATA,
                 * которая служит тут как device_id */
                SP_DEVICE_INTERFACE_DETAIL_DATA *devdetail;
                DWORD path_size = pDevIface->dbcc_size - offsetof(DEV_BROADCAST_DEVICEINTERFACE, dbcc_name);
                DWORD devdetail_size = path_size + sizeof(devdetail->cbSize);

                devdetail = malloc(devdetail_size);
                if (devdetail != NULL) {
                    DWORD i;

                    devdetail->cbSize = devdetail_size;
                    path_size/=sizeof(TCHAR);

                    /* для корректной проверки имени файла устройства и сравнения
                     * с другими переводим всегда в нижний регистр */
                    for (i=0; i < path_size; i++) {
                        devdetail->DevicePath[i] = _totlower(pDevIface->dbcc_name[i]);
                    }

                    /* проверяем по имени, крейт ли это */
                    if (f_devpath_get_crate_type(devdetail->DevicePath)!=LTR_CRATE_TYPE_UNKNOWN) {
                        /* сохраняем информацию в очереде */
                        f_devnotifies[f_devnotify_put_pos].iface_detail = devdetail;
                        f_devnotifies[f_devnotify_put_pos].type = type;
                        /* взводим событие, когда уже информация сохранена, чтобы
                           основной поток обработал информацию */
                        SetEvent(f_devnotify_event);
                        if (++f_devnotify_put_pos==LTRD_DEVNOTIFY_ARRAY_SIZE)
                            f_devnotify_put_pos = 0;
                    } else {
                        free(devdetail);
                    }
                }
            }
        }

        return 0;
    }

#endif
