// *** rd0xAB.cpp, main API file, Ver. 1.7, Windows ***
#pragma warning (disable:4800 4996)
#include <windows.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif
#ifdef __BUILDMACHINE__
#pragma message("*** rd0xAB Ver.1.7: compile in WinDDK ***")
#include <setupapi.h>
#include <hidsdi.h>
#else
#pragma message("*** rd0xAB Ver.1.7: compile in Visual Studio ***")
#include "ddk\inc\setupapi.h"
#include "ddk\inc\hidsdi.h"
#endif  // __BUILDMACHINE__
#ifdef __cplusplus
}
#endif

#include "rd0xAB.h"

#define TIMEOUT_RX              600             // receiving timeout
#define TIMEOUT_TX              600             // transmitting timeout
#define TIMEOUT_WT              2000            // timeout for wait

#define USB_VER                 0x0110          // USB revision
#define USB_VID                 0x10C4          // USB VID
#define USB_PID                 0x6884          // USB PID
#define USN_ID                  "CCR"           // device ID in USB serial

#define CFRAME_MRK              0x55            // CFRAME marker
#define CFRAME_LEN              3               // CFRAME length
#define SYNC_RPT                3               // number of attempts to achieve synchronization

#define TXQ_CN                  5               // timeout for start USB transmitting
#define UREPID_IN               0xAA            // IN-endpoint marker
#define UREPID_OUT              0x55            // OUT-endpoint marker
#define UREPORT_SZ              0x08            // length of USB frame (UREPID & datalen incl.)

// ******************
// *** primitives ***
// ******************
// USB queues processor; return TRUE if hardware error
BOOL usbQueue(PCCR pccr)
{
    BYTE        hidreport[UREPORT_SZ];
    DWORD       blksz = 0;
    BOOL        bErr = FALSE;

    memset(hidreport, 0, sizeof(hidreport));

    // sending data from the output queue
    if ((GetTickCount() > pccr->txq) && (pccr->outlen))
    {
        pccr->txq = GetTickCount() + TXQ_CN;
        blksz = (pccr->outlen > (UREPORT_SZ - 2)) ? UREPORT_SZ - 2 : pccr->outlen;
        hidreport[0] = UREPID_OUT;  // report ID
        hidreport[1] = (BYTE)blksz; // size of data
        memmove(&hidreport[2], pccr->outbuf, blksz);
        pccr->outlen -= (BYTE)blksz;
        if (pccr->outlen) memmove(pccr->outbuf, &pccr->outbuf[blksz], pccr->outlen);
        if (libusb_control_transfer(
            pccr->hL.u,
            LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
            0x09,                   // HID_SET_REPORT
            (0x02<<8) | 0x00,       // HID_REPORT_TYPE_OUTPUT
            0,                      // interface number
            hidreport,              // data
            UREPORT_SZ,             // size of data
            pccr->txout) != UREPORT_SZ) // TX timeout, number of tx bytes
        {
            bErr = TRUE;    // TX error
        }
    }
    // receiving data to the input queue
    if (pccr->inlen == 0)
    {
        // struct init for request
        if (libusb_control_transfer(
                pccr->hL.u,
                LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
                0x01,               // HID_GET_REPORT
                (0x01<<8) | 0x00,   // HID_REPORT_TYPE_INPUT
                0,                  // interface number
                hidreport,          // data
                UREPORT_SZ,         // size of data
                pccr->rxout) == UREPORT_SZ)
        {
            if ((hidreport[0] == UREPID_IN) &&
                (hidreport[1] != 0) &&
                (hidreport[1] <= (UREPORT_SZ - 2)))
            {
                if (hidreport[1] > (HIDBUF_SZ - pccr->inlen))
                {
                    blksz = HIDBUF_SZ - pccr->inlen;
                }
                else
                {
                    blksz = (DWORD)hidreport[1];
                }
                memmove(&pccr->inbuf[pccr->inlen], &hidreport[2], blksz);
                pccr->inlen += (BYTE)blksz;
            }
        }
        else
        {
            bErr = TRUE;    // RX error
        }
    }
    return bErr;
}

// test device handle
DLL_API BOOL link_isopen(PCCR pccr)
{
    if (pccr)
    {
        if (pccr->inbuf)    // check for USB
        {
            return ((pccr->hL.u) && (pccr->hL.u != INVALID_HANDLE_VALUE)) ? TRUE : FALSE;
        }
        else
        {
            return ((pccr->hL.t) && (pccr->hL.t != INVALID_HANDLE_VALUE)) ? TRUE : FALSE;
        }
    }
    return FALSE;
}

// close device handle
DLL_API void link_close(PCCR pccr)
{
    if (pccr)
    {
        if (pccr->inbuf)    // check for USB
        {
            if ((pccr->hL.u) && (pccr->hL.u != INVALID_HANDLE_VALUE))
            {
                 libusb_release_interface(pccr->hL.u, 0);    // logical detach
                 libusb_close(pccr->hL.u);                   // close USB handle
                 GlobalFree((HGLOBAL)pccr->inbuf);           // memory free
                 libusb_exit(NULL);                          // deinitialize library
            }
        }
        else
        {
            if ((pccr->hL.t) && (pccr->hL.t != INVALID_HANDLE_VALUE)) CloseHandle(pccr->hL.t);  // close TTY handle
        }
        memset(pccr, 0, sizeof(CCR));                        // clear struct
    }
}

// open USB device; return LERR_SUCCESS, LERR_HARDWARE
DLL_API DWORD link_hidopen(DWORD idx, PDWORD pusn, PCCR pccr)
{
    libusb_device               **devs = NULL;  // pointer to pointer of device, used to retrieve a list of devices
    libusb_device_handle        *husb = NULL;   // libusb device handle
    ssize_t                     usbcnt, cnt;    // counter of devices in list

    DWORD                       err = LERR_FAIL;
    DWORD                       hidcnt = 0, sni;
    char                        snbuf[32];
    BOOL                        fnd = FALSE;

    // initialize libusb default context
    if (libusb_init(NULL) < 0) return LERR_FAIL;
    libusb_set_debug(NULL, 0);
    // check pccr out pointer
    if (pccr) memset(pccr, 0, sizeof(CCR));     // clear struct
    // get the list of devices
    if ((usbcnt = libusb_get_device_list(NULL, &devs)) < 0) goto ferr_exit;
    err = LERR_HARDWARE;
    if (usbcnt == 0) goto ferr_exit;
    // check for vid & pid
    for(cnt = 0; cnt < usbcnt; cnt++)
    {
        struct libusb_device_descriptor      devdsc;     // temporary device descriptor
        libusb_device                        *device;    // temporary device path

        device = devs[cnt];                     // set device path
        memset(&devdsc, 0, sizeof(devdsc));     // clear description
        // get device descriptor
        if (libusb_get_device_descriptor(device, &devdsc) < 0) continue;
        if ((devdsc.idVendor == USB_VID) && (devdsc.idProduct == USB_PID))
        {
            BOOL        drv_act = FALSE;
            //* read ID & SN *
            if (libusb_open(device, &husb)) continue;

            // try detach hid driver
            if (libusb_kernel_driver_active(husb, 0) == 1)
            {
                if (libusb_detach_kernel_driver(husb, 0)) goto lhop_sclose;
                drv_act = TRUE;
            }
            if (libusb_claim_interface(husb, 0) == 0)   // claim interface
            {
                int     r;
                memset(snbuf, 0, sizeof(snbuf));        // clear SN buffer
                // get serial from device
                libusb_get_string_descriptor_ascii(husb, devdsc.iSerialNumber,(PBYTE)snbuf, sizeof(snbuf));
                if ((r=libusb_get_string_descriptor_ascii(husb, devdsc.iSerialNumber, (PBYTE)snbuf, sizeof(snbuf))) > 0)
                {
                    snbuf[sizeof(USN_ID) / sizeof(char) - 1] = 0;   // set terminator
                    if (lstrcmpA(snbuf, USN_ID)) goto lhop_srelease;
                    // read SN from USB serial
                    sni = 0; sscanf(&snbuf[sizeof(USN_ID) / sizeof(char)], "%X", &sni);
                    if (sni == 0) goto lhop_srelease;
                    if ((pusn) && (*pusn))
                    {
                        if (sni == *pusn) fnd = TRUE;
                    }
                    else
                    {
                        if (idx == hidcnt) fnd = TRUE;
                    }
                    hidcnt++;   // index increment
                }
            }
            if (fnd) break;
lhop_srelease:
            libusb_release_interface(husb, 0);                      // logical detach
lhop_sclose:
            libusb_close(husb);                                     // close device if error
            husb = NULL;
        }
    }
    libusb_free_device_list(devs, 1);    // free the list of devices
    if (!fnd) goto ferr_exit;
    else
    {
        if (pusn) *pusn = sni;  // set SN
    }
    // fill output struct
    if (pccr)
    {
        PBYTE       tbuf;
        if (tbuf = (PBYTE)GlobalAlloc(GPTR, HIDBUF_SZ * 2))
        {
            pccr->hL.u = husb;                              // USB handle
            pccr->txq = GetTickCount();                     // tx quantile
            pccr->rxout = TIMEOUT_RX;                       // rx timeout
            pccr->txout = TIMEOUT_TX;                       // tx timeout
            pccr->wtout = TIMEOUT_WT;                       // wait timeout
            pccr->inbuf = tbuf;                             // in-queue
            pccr->inlen = 0;                                // length of in-queue
            pccr->outbuf = &tbuf[HIDBUF_SZ];                // out-queue
            pccr->outlen = 0;                               // length of out-queue
        }
        else
            goto ferr_exit;                                 // LERR_HARDWARE
    }
    err = LERR_SUCCESS;
ferr_exit:
    if ((husb) && ((err != LERR_SUCCESS) || (!pccr)))
    {
        libusb_release_interface(husb, 0);      // logical detach
        libusb_close(husb);                     // close handle if memory fail
    }
    return err; // operation complete
}

// open COM port; return LERR_SUCCESS, LERR_HARDWARE
DLL_API DWORD link_comopen(DWORD port, DWORD cbr, PCCR pccr)
{
    HANDLE                              handle = INVALID_HANDLE_VALUE;
    TCHAR                               szLocal[16];
    DCB                                 dcb;
    COMMTIMEOUTS                        ct;
    BOOL                                bErr = TRUE;

    _stprintf_s(szLocal, TEXT("\\\\.\\COM%u"), port);

    if ((handle = CreateFile(szLocal, GENERIC_READ | GENERIC_WRITE,
        0, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE) goto ferr_exit;
    if (!GetCommState(handle, &dcb)) goto ferr_exit;
    dcb.BaudRate = cbr;                         // set the baud rate
    dcb.fBinary = TRUE;                         // binary protocol
    dcb.fParity = FALSE;                        // parity none
    dcb.fOutxCtsFlow = FALSE;                   // CTS output flow control
    dcb.fOutxDsrFlow = FALSE;                   // DSR output flow control
    dcb.fDtrControl = DTR_CONTROL_DISABLE;      // DTR flow control type
    dcb.fDsrSensitivity = FALSE;                // DSR sensitivity
    dcb.fTXContinueOnXoff = FALSE;              // XOFF continues Tx
    dcb.fOutX = FALSE;                          // XON/XOFF out flow control
    dcb.fInX = FALSE;                           // XON/XOFF in flow control
    dcb.fErrorChar = FALSE;                     // enable error replacement
    dcb.fNull = FALSE;                          // enable null stripping
    dcb.fRtsControl = RTS_CONTROL_DISABLE;      // RTS flow control
    dcb.fAbortOnError = FALSE;                  // abort reads/writes on error
    dcb.wReserved = 0;                          // not currently used
    dcb.XonLim = 1;                             // transmit XON threshold
    dcb.XoffLim = 1;                            // transmit XOFF threshold
    dcb.ByteSize = 8;                           // number of bits/byte, 4-8
    dcb.Parity = NOPARITY;                      // no parity bit
    dcb.StopBits = ONESTOPBIT;                  // 0,1,2 = 1, 1.5, 2
    dcb.XonChar = 0x1B;                         // Tx and Rx XON character
    dcb.XoffChar = 0x1C;                        // Tx and Rx XOFF character
    dcb.ErrorChar = 0x1B;                       // error replacement character
    dcb.EofChar = 0x1C;                         // end of input character
    dcb.EvtChar = (BYTE)0x80;                   // received event character
    if (!SetCommState(handle, &dcb)) goto ferr_exit;    // set new port mode
    ct.ReadIntervalTimeout = 0;
    ct.ReadTotalTimeoutMultiplier = 0;
    ct.ReadTotalTimeoutConstant = TIMEOUT_RX;
    ct.WriteTotalTimeoutMultiplier = 0;
    ct.WriteTotalTimeoutConstant = TIMEOUT_TX;
    if (!SetCommTimeouts(handle, &ct)) goto ferr_exit;  // set new timeouts
    if (!SetupComm(handle, 2, 2)) goto ferr_exit;       // set port queue size
    if (!PurgeComm(handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) goto ferr_exit;
    if (!EscapeCommFunction(handle, CLRDTR)) goto ferr_exit;    // set new DTR state
    if (!EscapeCommFunction(handle, SETRTS)) goto ferr_exit;    // set new RTS state
    bErr = FALSE;
ferr_exit:
    if (bErr)   // if port error
    {
        if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle);    // close handle if port initialization error
        return LERR_HARDWARE;
    }
    // fill output struct if success
    if (pccr)
    {
        PBYTE       tbuf;
        if (tbuf = (PBYTE)GlobalAlloc(GPTR, HIDBUF_SZ * 2))
        {
            pccr->hL.t = handle;                // handle
            pccr->txq = GetTickCount();         // tx quantile
            pccr->rxout = TIMEOUT_RX;           // rx timeout
            pccr->txout = TIMEOUT_TX;           // tx timeout
            pccr->wtout = TIMEOUT_WT;           // wait timeout
            pccr->inbuf = NULL;                 // in-queue
            pccr->inlen = 0;                    // length of in-queue
            pccr->outbuf = NULL;                // out-queue
            pccr->outlen = 0;                   // length of out-queue
        }
        else
        {
            CloseHandle(handle);    // close handle if memory fail
            return LERR_HARDWARE;
        }
    }
    else CloseHandle(handle);       // close handle if not pointer to struct
    return LERR_SUCCESS;            // operation complete
}

// queues flush; return LERR_SUCCESS, LERR_HARDWARE
DLL_API DWORD link_flush(PCCR pccr)
{
    if (!pccr) return LERR_HARDWARE;
    if (pccr->inbuf)            // pointer not zero in usb mode
    {
        if (usbQueue(pccr)) return LERR_HARDWARE;
    }
    else
    {
        PurgeComm(pccr->hL.t, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
    }
    pccr->inlen = 0; pccr->outlen = 0;
    return LERR_SUCCESS;
}

// byte receiving; return LERR_SUCCESS, LERR_HARDWARE, LERR_TIMEOUT_RX
DWORD link_rxbyte(PCCR pccr, PBYTE pdt)
{
    DWORD           stoptick;
    if (!pccr) return LERR_HARDWARE;
    for (stoptick = GetTickCount() + pccr->rxout; GetTickCount() < stoptick;)
    {
        if (pccr->inbuf)
        {   // usb processing
            if (usbQueue(pccr)) return LERR_HARDWARE;
            if (pccr->inlen)
            {
                if (pdt) *pdt = pccr->inbuf[0];
                if (--pccr->inlen) memmove(pccr->inbuf, &pccr->inbuf[1], pccr->inlen);
                return LERR_SUCCESS;
            }
        }
        else
        {   // com processing
            DWORD           readen = 0;
            BYTE            tdt;
            if (!ReadFile(pccr->hL.t, &tdt, 1, &readen, NULL)) return LERR_HARDWARE;
            if (readen)
            {
                if (pdt) *pdt = tdt;
                return LERR_SUCCESS;
            }
        }
    }
    return LERR_TIMEOUT_RX;
}

// byte transmitting; return LERR_SUCCESS, LERR_HARDWARE, LERR_TIMEOUT_TX
DWORD link_txbyte(PCCR pccr, BYTE dt)
{
    DWORD           stoptick;
    if (!pccr) return LERR_HARDWARE;
    for (stoptick = GetTickCount() + pccr->txout; GetTickCount() < stoptick;)
    {
        if (pccr->inbuf)
        {   // usb processing
            if (usbQueue(pccr)) return LERR_HARDWARE;
            if (pccr->outlen < HIDBUF_SZ)
            {
                pccr->outbuf[pccr->outlen++] = dt;
                return LERR_SUCCESS;
            }
        }
        else
        {   // com processing
            DWORD       written = 0;
            if (!WriteFile(pccr->hL.t, &dt, 1, &written, NULL)) return LERR_HARDWARE;
            if (written) return LERR_SUCCESS;
        }
    }
    return LERR_TIMEOUT_TX;
}

// byte transmitting then echo receiving; return LERR_SUCCESS, LERR_HARDWARE, LERR_TIMEOUT_RX, LERR_TIMEOUT_TX, LERR_NOTSYNC
DLL_API DWORD link_echobyte(PCCR pccr, BYTE dt)
{
    BYTE            val;
    DWORD           err;
    if (!pccr) return LERR_HARDWARE;
    if ((err = link_txbyte(pccr, dt)) != LERR_SUCCESS) goto ferr_exit;
    if ((err = link_rxbyte(pccr, &val)) != LERR_SUCCESS) goto ferr_exit;
    if (val != dt) return LERR_NOTSYNC;
ferr_exit:
    return err;
}

// waiting marker; return LERR_SUCCESS, LERR_FAIL, LERR_HARDWARE, LERR_NOTSYNC, LERR_TIMEOUT_RX
DWORD link_waitmarker(PCCR pccr, PBYTE pmrk)
{
    DWORD           stoptick;
    BYTE            val;
    DWORD           err;

    if (!pccr) return LERR_HARDWARE;
    for (stoptick = GetTickCount() + pccr->rxout; GetTickCount() < stoptick;)
    {
        if ((err = link_rxbyte(pccr, &val)) == LERR_HARDWARE) return err;
        if (err == LERR_SUCCESS)
        {
            if (pmrk) *pmrk = val;
            if (val == IO_CHBUSY)
            {
                stoptick = GetTickCount() + pccr->wtout;
                continue;
            }
            if ((val == IO_CHOK) || (val == IO_CHRDY) ||
                (val == IO_CHDAT) || (val == IO_CHREQ)) return LERR_SUCCESS;
            if (val == IO_CHFAIL) return LERR_FAIL;
            return LERR_NOTSYNC;
        }
    }
    return LERR_TIMEOUT_RX;
}

// receiving data frame - DFRAME; return LERR_SUCCESS, LERR_HARDWARE, LERR_NOTSYNC, LERR_TIMEOUT_RX
DWORD link_rxblock(PCCR pccr, PBYTE buf, WORD len)
{
    BYTE            val, sum8;
    WORD            cnt;
    DWORD           err;

    if (!pccr) return LERR_HARDWARE;
    for (sum8 = 0, cnt = 0; cnt < len; cnt++)
    {
        if ((err = link_rxbyte(pccr, &val)) != LERR_SUCCESS) return err;
        buf[cnt] = val;     // store byte
        sum8 += val;        // crc calculate
    }
    if ((err = link_rxbyte(pccr, &val)) != LERR_SUCCESS) return err;    // crc receiving
    if (sum8 != val) return LERR_NOTSYNC;   // crc check
    return LERR_SUCCESS;
}

// transmitting data frame - DFRAME; return LERR_SUCCESS, LERR_HARDWARE, LERR_TIMEOUT_TX
DWORD link_txblock(PCCR pccr, PBYTE buf, WORD len)
{
    BYTE            val, sum8;
    WORD            cnt;
    DWORD           err;

    if (!pccr) return LERR_HARDWARE;
    for (sum8 = 0, cnt = 0; cnt < len; cnt++)
    {
        val = buf[cnt];     // store byte
        if ((err = link_txbyte(pccr, val)) != LERR_SUCCESS) return err;
        sum8 += val;        // crc calculate
    }
    if ((err = link_txbyte(pccr, sum8)) != LERR_SUCCESS) return err;    // crc transmitting
    return LERR_SUCCESS;
}

// transmitting command frame - CFRAME
DWORD link_txcmd(PCCR pccr, BYTE cm, BYTE parm)
{
    BYTE            buf[CFRAME_LEN];
    DWORD           err, cnt;

    if (!pccr) return LERR_HARDWARE;
    // build CFRAME
    buf[0] = cm; buf[1] = parm; buf[2] = CFRAME_MRK;
    // crc calculate
    for (cnt = 0; cnt != (CFRAME_LEN - 1); cnt++) buf[CFRAME_LEN - 1] ^= buf[cnt];
    // sync starting
    for (cnt = 0; cnt != 2; cnt++)
    {
        BYTE            t, rpt;

        if ((err = link_flush(pccr)) != LERR_SUCCESS) return err;       // queue flush
        if (((err = link_echobyte(pccr, CFRAME_MRK)) != LERR_SUCCESS) &&
            (err != LERR_TIMEOUT_RX) && (err != LERR_NOTSYNC)) return err;
        if (err == LERR_SUCCESS)
        {
            // посылка команды
            for (t = 0; t != CFRAME_LEN; t++)
                if ((err = link_txbyte(pccr, buf[t])) != LERR_SUCCESS) return err;
            return err;
        }
        for (rpt = 0; rpt != SYNC_RPT; rpt++)
        {
            // sync sequence
            if ((err = link_flush(pccr)) != LERR_SUCCESS) return err;   // queue flush
            for (t = 0; t < PINGLEN_CN; t++)
            {
                if (((err = link_echobyte(pccr, t + IO_CHPING)) != LERR_SUCCESS) &&
                    (err != LERR_TIMEOUT_RX) && (err != LERR_NOTSYNC)) return err;
                if (err != LERR_SUCCESS) break;
            }
            if (err == LERR_SUCCESS) break;
        }

    }
    return LERR_NOTSYNC;
}

// wait for request from reader; return LERR_SUCCESS, LERR_HARDWARE, LERR_NOTSYNC, LERR_TIMEOUT_RX
DLL_API DWORD link_rqwait(PCCR pccr, PBYTE buf, BYTE len)
{
    BYTE            val;
    DWORD           err;

    if ((!pccr) || (!buf) || (!len)) return LERR_HARDWARE;
    if ((err = link_waitmarker(pccr, &val)) != LERR_SUCCESS) return err;
    if (val != IO_CHREQ) return LERR_NOTSYNC;
    return link_rxblock(pccr, buf, len);
}

// send acknowledge to reader; return LERR_SUCCESS, LERR_HARDWARE, LERR_NOTSYNC, LERR_TIMEOUT_RX, LERR_TIMEOUT_TX
DLL_API DWORD link_rqack(PCCR pccr, BYTE ack)
{
    DWORD           err;
    if (!pccr) return LERR_HARDWARE;

    if ((err = link_txbyte(pccr, IO_CHREQ)) != LERR_SUCCESS) return err;
    if ((err = link_txblock(pccr, &ack, 1)) != LERR_SUCCESS) return err;
    if ((err = link_waitmarker(pccr, &ack)) != LERR_SUCCESS) return err;
    if (ack != IO_CHOK) return LERR_FAIL;
    return err;
}

// main function for reader IO
DLL_API DWORD link_packet(PCCR pccr, BYTE cmd, BYTE parm, PBYTE outbuf, WORD outlen, PBYTE inbuf, WORD inlen)
{
    BYTE        mrk;
    DWORD       err;

    if (!pccr) return LERR_HARDWARE;

    if ((err = link_txcmd(pccr, cmd, parm)) != LERR_SUCCESS) return err;
    if ((outlen) && (outbuf))
    {
        if ((err = link_waitmarker(pccr, &mrk)) != LERR_SUCCESS) return err;
        if (mrk != IO_CHRDY) return LERR_NOTSYNC;
        if ((err = link_txblock(pccr, outbuf, outlen)) != LERR_SUCCESS) return err;
    }
    if ((inlen) && (inbuf))
    {
        if ((err = link_waitmarker(pccr, &mrk)) != LERR_SUCCESS) return err;
        if (mrk != IO_CHDAT) return LERR_NOTSYNC;
        if ((err = link_rxblock(pccr, inbuf, inlen)) != LERR_SUCCESS) return err;
    }
    if ((err = link_waitmarker(pccr, &mrk)) != LERR_SUCCESS) return err;
    if ((mrk != IO_CHOK) && (mrk != IO_CHFAIL)) err = LERR_NOTSYNC;
    return err;
}
