#define USBTMCLIB_BASIC_C
#include "usbtmclib/usbtmclib_basic.h"
#include "app/scpi/scpi_core.h"

#include <string.h>

static eTMCLibStatus_t tmclib_bulkin_check_inprogress(sUSBTMCContext_t*);
static eTMCLibStatus_t tmclib_bulkout_check_inprogress(sUSBTMCContext_t*);
static eTMCLibStatus_t tmclib_new_data_transfer(sUSBTMCContext_t*, sUSBTransfer_t *);
static eTMCLibStatus_t tmclib_bulkout_process( sUSBTMCContext_t *, sUSBTransfer_t * );
static eTMCLibStatus_t tmclib_bulkin_process( sUSBTMCContext_t *, sUSBTransfer_t * );
static eTMCLibStatus_t tmclib_interruptin_process( sUSBTMCContext_t *, sUSBTransfer_t * );
static void tmclib_bulkout_error( sUSBTMCContext_t *, sUSBTransfer_t *, eTMCLibStatus_t );
static void tmclib_bulkin_error( sUSBTMCContext_t *, sUSBTransfer_t *, eTMCLibStatus_t );
static void tmclib_interruptin_error( sUSBTMCContext_t *, sUSBTransfer_t *, eTMCLibStatus_t );


static sUSBTMCContext_t ctxUsbTmc;
static eTMCLibStatus_t lastRequestStatus = tmclib_status_success;
#if DEBUG_USBTMC > 0 // SCPI debug only
volatile uint32_t gDebugUSBTMC_TxBytes = 0; 
#endif
// =================================================================================
// tmclib_cleanup_bulkout()
static void tmclib_cleanup_bulkout_header( sUSBTMCContext_t * ctx )
{
    ctx->bulkOutHeader.bTag = 0;
    ctx->bulkOutHeader.bTagInverse = 0;
    ctx->bulkOutHeader.MsgID = eUsbtmcMsg_Reserved;
    ctx->bulkOutHeader.Reserved = 0;

    memset( ctx->bulkOutHeader.cmdSpec.rawBytes, 0, 
     sizeof(ctx->bulkOutHeader.cmdSpec.rawBytes) );
}
// =================================================================================
// tmclib_cleanup_bulkin()
static void tmclib_cleanup_bulkin_header( sUSBTMCContext_t * ctx )
{
    ctx->bulkInHeader.bTag = 0;
    ctx->bulkInHeader.bTagInverse = 0;
    ctx->bulkInHeader.MsgID = eUsbtmcMsg_Reserved;
    ctx->bulkInHeader.Reserved = 0;

    memset( ctx->bulkInHeader.cmdSpec.rawBytes, 0, 
     sizeof(ctx->bulkInHeader.cmdSpec.rawBytes) );
}
// =================================================================================
// tmclib_cleanup_interruptin()
static void tmclib_cleanup_interruptin_header( sUSBTMCContext_t * ctx )
{
    memset( &ctx->interruptInHeader, 0, sizeof(ctx->interruptInHeader) );    
}
// =================================================================================
// tmclib_write_bulkin_header()
// Writes current bulk-in header (DevDepMsgIn) into output transfer.
static bool tmclib_write_bulkin_header( sUSBTMCContext_t * ctx )
{
    (void)ctx->bulkInHeader.bTag;
    (void)ctx->bulkInHeader.bTagInverse;
    (void)ctx->bulkInHeader.MsgID;
    (void)ctx->bulkInHeader.Reserved;
    (void)ctx->bulkInHeader.cmdSpec.DevDepMsgIn.transferSize;
    (void)ctx->bulkInHeader.cmdSpec.DevDepMsgIn.Reserved;
    (void)ctx->bulkInHeader.cmdSpec.DevDepMsgIn.bmTransferAttributes;

    // remember the raw Bulk-In header to be able to modify data later
    ctx->psActiveInHeader = (sUSBTMCBulkInHeader_t*)usb_transfer_raw_read(ctx->bulkInState.transf);

    // push entire header
    if( !usb_push_transfer( ctx->bulkInState.transf,
                       ctx->bulkInHeader.rawBytes,
                       sizeof(ctx->bulkInHeader) ) )
    {
        ctx->psActiveInHeader = NULL; 
        return false;
    }

    return true;
}

// =================================================================================
// tmclib_update_bulkin()
// Writes current bulk-in header (DevDepMsgIn) into output transfer.
static bool tmclib_update_bulkin( sUSBTMCContext_t * ctx )
{
    my_assert( ctx );

    if( NULL != ctx->psActiveInHeader )
    {
        // update current transfer BulkIN header:
        ctx->psActiveInHeader->cmdSpec.DevDepMsgIn.transferSize 
          = ctx->bulkInState.transferSize; // update TransferSize field

        // check End-of-message indicator
        if( ctx->bulkInState.bEndOfMessage ) 
            ctx->psActiveInHeader->cmdSpec.DevDepMsgIn.bmTransferAttributes
              |= bm_DEV_DEP_MSG_IN_EOM;    // set End-Of-Message indicator
        else
            ctx->psActiveInHeader->cmdSpec.DevDepMsgIn.bmTransferAttributes
              &= ~bm_DEV_DEP_MSG_IN_EOM;   // reset End-Of-Message indicator

        return true;
    }

    return false;
}
// =================================================================================

// tmclib_write_notification_header()
// Writes current interrupt-in header into output transfer.
static bool tmclib_write_notification_header( sUSBTMCContext_t * ctx )
{
    // remember the raw Notification (Interrupt-In) header to be able to modify data later
    ctx->psActiveNotificationHeader = (sUSBTMCNotificationHeader_t*)usb_transfer_raw_read(ctx->interruptInState.transf);

    // push entire header
    if( !usb_push_transfer( ctx->interruptInState.transf,
                       ctx->interruptInHeader.rawBytes,
                       sizeof(ctx->interruptInHeader) ) )
    {
        ctx->psActiveNotificationHeader = NULL; 
        return false;
    }

    // only for interrupt-in transfer: calculate the bytes including the header bytes
    ctx->interruptInState.bytesCounter += sizeof(ctx->interruptInHeader);

    return true;
}
// =================================================================================
// tmclib_update_notification()
// Writes current notification header into output transfer.
static bool tmclib_update_notification( sUSBTMCContext_t * ctx )
{
    my_assert( ctx );

    if( NULL != ctx->psActiveNotificationHeader )
    {
        // update current transfer BulkIN header:
        USBTMC_USB488NotificationHeader_SetTag( ctx->interruptInHeader, ctx->interruptInState.bTag );
        USBTMC_USB488NotificationHeader_SetSTB( ctx->interruptInHeader, ctx->interruptInState.STB );
        USBTMC_USB488NotificationHeader_SetTag( *(ctx->psActiveNotificationHeader), ctx->interruptInState.bTag );
        USBTMC_USB488NotificationHeader_SetSTB( *(ctx->psActiveNotificationHeader), ctx->interruptInState.STB );
        
        return true;
    }

    return false;
}
// =================================================================================
// @tmclib_init()
// Initialize TMC library
// @rx - receiving buffer object
// @tx - transmitting buffer object
// @ntx - interrupt/notify transmitting buffer object
eTMCLibStatus_t tmclib_init( sUSBTransfer_t * rx, sUSBTransfer_t * tx, sUSBTransfer_t * ntx )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( usb_size_transfer( rx ) > SCPI_MAX_INPUT_COMMAND )
    {
        // Invalid value SCPI_MAX_INPUT_COMMAND. 
        // Maximum input command length shall be at least equal to @rx size or more (see 'USB_BULK_USBTMC_RX_BUFFER')
        // The longest input packet must fit to the command buffer.
        // This check can not be performed in compile time due to isolation between low-level buffer and SCPI parser level.
        my_assert( usb_size_transfer( rx ) <= SCPI_MAX_INPUT_COMMAND );
    
        return tmclib_status_invalid_length;
    }

    tmclib_cleanup_bulkout_header( ctx );
    tmclib_cleanup_bulkin_header( ctx );
    tmclib_cleanup_interruptin_header( ctx );

    ctx->bulkOutState.bInProgress = false;
    ctx->bulkOutState.bEndOfMessage = false;
    ctx->bulkOutState.transferSize = 0;
    ctx->bulkOutState.bytesCounter = 0;
    ctx->bulkOutState.transf = rx;
    ctx->bulkOutState.bNewTransfer = false;
    ctx->bulkOutState.bLatestTag = 0;

    ctx->bulkInState.bInProgress = false;
    ctx->bulkInState.bEndOfMessage = false;
    ctx->bulkInState.transferSize = 0;
    ctx->bulkInState.bytesCounter = 0;
    ctx->bulkInState.transf = tx;
    ctx->bulkInState.bNewTransfer = false;
    ctx->bulkInState.bLatestTag = 0;

    ctx->interruptInState.bInProgress = false;
    ctx->interruptInState.bEndOfMessage = false;
    ctx->interruptInState.bytesCounter = 0;
    ctx->interruptInState.bTag = 0;  ///???
    ctx->interruptInState.STB = 0;   ///???
    ctx->interruptInState.transf = ntx;
    ctx->interruptInState.bNewTransfer = false;

    if( eScpiStatus_success != sSCPILibHandle.fInit() )
    {
        return (lastRequestStatus = tmclib_status_failure);
    }    
    
    return (lastRequestStatus = tmclib_status_success);
}
// =================================================================================
// @tmclib_deinit()
// Deinitialize TMC library
eTMCLibStatus_t tmclib_deinit()
{
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkout(0, true) );
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_bulkin(0, true) );
    tmclib_generic_event( COMPOUND_tmclib_event_context_abort_interruptin(0, true) );

    sSCPILibHandle.fDeInit();

    return (lastRequestStatus = tmclib_status_success);
}
// =================================================================================
// @tmclib_abort_bulkout()
// Aborts currently running BulkOUT transfer
// @eventCtx->abort:
//  * @transfer_bTag - The bTag value associated with the transfer to abort, see 4.2.1.2 [1]
//  * @force - makes to ignore the @transfer_bTag if true
// Returns: 
//   in case of success: @tmclib_status_success
//   in case of error:   @tmclib_status_not_in_progress
eTMCLibStatus_t tmclib_abort_bulkout( sTMCEventContext_t * eventCtx )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( eventCtx->event != eTMCEventBulkOutStop )
    {
        my_assert( eventCtx->event == eTMCEventBulkOutStop );
        return (lastRequestStatus = tmclib_status_invalid_param); // invalid event
    }

    if( eventCtx->abort.force || ctx->bulkOutState.bInProgress )
    {
        if( eventCtx->abort.force || (eventCtx->abort.transfer_bTag == ctx->bulkOutHeader.bTag) )
        {
            ctx->bulkOutState.bLatestTag = ctx->bulkOutHeader.bTag; // remember latest bTag
            ctx->bulkOutState.bInProgress = false;
            ctx->bulkOutState.bNewTransfer = false;
            (void)ctx->bulkOutState.bytesCounter; // do not clear the last transfer byte counter to respond with

            // force cancellation: forget about latest transaction
            if( eventCtx->abort.force ) ctx->bulkOutState.bLatestTag = 0;

            tmclib_cleanup_bulkout_header( ctx );

            // check if No-Reset-Transfer feature is requested
            if( !eventCtx->abort.noResetTransfer ) 
            {usb_reset_transfer( ctx->bulkOutState.transf );}     // Not requested, just reset transfer
            else
            {usb_transfer_compress( ctx->bulkOutState.transf );}  // Requested, compress transfer

            return (lastRequestStatus = tmclib_status_success);
        }
    }

    // check for already aborted transfer:
    if( (eventCtx->abort.transfer_bTag == ctx->bulkOutState.bLatestTag) )
    {
        // forget about latest transaction: set zero value
        ctx->bulkOutState.bLatestTag = 0;

        // this transfer has been already aborted: return 'success' code for previous aborted transfer
        return (lastRequestStatus = tmclib_status_success);
    }

    // Error: BulkOut transfer is not in progress
    return (lastRequestStatus = tmclib_status_not_in_progress);
}
// =================================================================================
// @tmclib_abort_bulkin()
// Aborts currently running BulkIN transfer
// @eventCtx->abort:
//  * @transfer_bTag - The bTag value associated with the transfer to abort, see 4.2.1.4 [1]
//  * @force - makes to ignore the @transfer_bTag if true
// Returns: 
//   in case of success: @tmclib_status_success
//   in case of error:   @tmclib_status_not_in_progress
eTMCLibStatus_t tmclib_abort_bulkin( sTMCEventContext_t * eventCtx )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( eventCtx->event != eTMCEventBulkInStop )
    {
        my_assert( eventCtx->event == eTMCEventBulkInStop );
        return (lastRequestStatus = tmclib_status_invalid_param); // invalid event
    }

    if( eventCtx->abort.force || ctx->bulkInState.bInProgress )
    {
        if( eventCtx->abort.force || (eventCtx->abort.transfer_bTag == ctx->bulkInHeader.bTag) )
        {   
            ctx->bulkInState.bLatestTag = ctx->bulkInHeader.bTag; // remember latest bTag
            ctx->bulkInState.bInProgress = false;
            ctx->bulkInState.bNewTransfer = false;
            ctx->psActiveInHeader = NULL;
            (void)ctx->bulkInState.bytesCounter; // do not clear the last transfer byte counter to respond with

            // force cancellation: forget about latest transaction
            if( eventCtx->abort.force ) ctx->bulkInState.bLatestTag = 0;

            tmclib_cleanup_bulkin_header( ctx );

            // @noResetTransfer - not supported for BulkIN
            my_assert( !eventCtx->abort.noResetTransfer ); 

            usb_reset_transfer( ctx->bulkInState.transf );

            // reset short packet indicator?
            if( eventCtx->abort.clearShortPacketIndicator )
            {
                // reset short packet indicator to NOT TO SEND zero packet
                usb_transfer_clear_shortpacket( ctx->bulkInState.transf );
            }
    
            return (lastRequestStatus = tmclib_status_success);
        }
    }

    // check for already aborted transfer:
    if( (eventCtx->abort.transfer_bTag == ctx->bulkInState.bLatestTag) )
    {
        // forget about latest transaction: set zero value
        ctx->bulkInState.bLatestTag = 0;

        // this transfer has been already aborted: return 'success' code for previous aborted transfer
        return (lastRequestStatus = tmclib_status_success);
    }

    // Error: BulkIn transfer is not in progress
    return (lastRequestStatus = tmclib_status_not_in_progress);
}
// =================================================================================
// @tmclib_abort_interruptin()
// Aborts currently running InterruptIN transfer
// @eventCtx->abort:
//  * @transfer_bTag - The bTag value associated with the transfer to abort, see 4.3.1 [2]
//  * @force - makes to ignore the @transfer_bTag if true
// Returns: 
//   in case of success: @tmclib_status_success
//   in case of error:   @tmclib_status_not_in_progress
static eTMCLibStatus_t tmclib_abort_interruptin( sTMCEventContext_t * eventCtx )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( eventCtx->event != eTMCEventInterruptInStop )
    {
        my_assert( eventCtx->event == eTMCEventInterruptInStop );
        return (lastRequestStatus = tmclib_status_invalid_param); // invalid event
    }

    if( eventCtx->abort.force || ctx->interruptInState.bInProgress )
    {
        if( eventCtx->abort.force || (eventCtx->abort.transfer_bTag == ctx->interruptInState.bTag ) )
        {
            ctx->interruptInState.bInProgress = false;
            ctx->interruptInState.bNewTransfer = false;
            ctx->interruptInState.bEndOfMessage = false;
            ctx->interruptInState.bytesCounter = 0;
            ctx->interruptInState.STB = 0;  ///???
            ctx->interruptInState.bTag = 0; ///???
            ctx->psActiveNotificationHeader = NULL;

            tmclib_cleanup_interruptin_header( ctx );

            // @noResetTransfer - not supported for InterruptIN
            my_assert( !eventCtx->abort.noResetTransfer ); 

            usb_reset_transfer( ctx->interruptInState.transf );

            return (lastRequestStatus = tmclib_status_success);
        }
    }

    // Error: BulkOut transfer is not in progress
    return (lastRequestStatus = tmclib_status_not_in_progress);
}

// =================================================================================
// @tmclib_complete_interruptin()
// Aborts currently running InterruptIN transfer
// @transfer_bTag - The bTag value associated with the transfer to abort, see 4.3.1 [2]
// @force - makes to ignore the @transfer_bTag if true
// Returns: 
//   in case of success: @tmclib_status_success
//   in case of error:   @tmclib_status_not_in_progress
eTMCLibStatus_t tmclib_complete_interruptin( uint8_t transfer_bTag, bool force )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( force || ctx->interruptInState.bInProgress )
    {
        if( force || (transfer_bTag == ctx->interruptInState.bTag ) )
        {
            ctx->interruptInState.bInProgress = false;
            ctx->interruptInState.bNewTransfer = false;
            ctx->interruptInState.bEndOfMessage = true;
            (void)ctx->interruptInState.bytesCounter;
            (void)ctx->interruptInState.STB;   ///???
            (void)ctx->interruptInState.bTag;  ///???
            (void)ctx->psActiveNotificationHeader;
            (void)&ctx->interruptInHeader;
            (void)ctx->interruptInState.transf;

            return (lastRequestStatus = tmclib_status_success);
        }
    }

    // Error: Interrupt-IN transfer is not in progress
    return (lastRequestStatus = tmclib_status_not_in_progress);
}

// =================================================================================
// @tmclib_new_data_transfer()
// Starts new BulkOUT/BulkIN transfer
// @ctx - the USBTMC context
static eTMCLibStatus_t tmclib_new_data_transfer( sUSBTMCContext_t * ctx, sUSBTransfer_t * rx )
{
   eTMCLibStatus_t status = tmclib_status_success;

   do
   {
       if( usb_count_transfer( rx ) < sizeof( ctx->bulkOutHeader ) )
       { 
           // 3.2 Bulk-OUT endpoint, [1]
           // error: BulkOut header is fragmented
           // Either the host sent the header fragmented, or the internal buffer
           // is too small. Processing is impossible.
           status = tmclib_status_header_fragmented_error;
           break;
       }

       // Read the BulkOut header from the transfer
       if( sizeof( ctx->bulkOutHeader ) != usb_read_transfer( rx, &ctx->bulkOutHeader, sizeof( ctx->bulkOutHeader ), false ) )
       {
           // error: can not read the header
           status = tmclib_status_read_error; 
           break;
       }

       if(  ( (1ul << 8*sizeof(ctx->bulkOutHeader.bTag)) - 1
              != 
            (ctx->bulkOutHeader.bTag ^ ctx->bulkOutHeader.bTagInverse))
       // @bTag must not be zero, 3.2 Bulk-OUT endpoint, [1]
         || 0 == ctx->bulkOutHeader.bTag )
       {
           // error: invalid bTag field
           // 3.2.2.3 Bulk-OUT transfer protocol errors, [1]
           status = tmclib_status_header_error; 
           break;
       }

       eUSBTMCMsgId_t msgId = (eUSBTMCMsgId_t)ctx->bulkOutHeader.MsgID;
       
       switch( msgId )
       {
            case eUsbtmcMsg_DevDepMsgOut:
            {
                if( 0 == ctx->bulkOutHeader.cmdSpec.DevDepMsgOut.transferSize )
                {
                    // error: invalid @transferSize
                    status = tmclib_status_header_error; 
                }
                else
                {
                    // Initialize BulkOut transfer
                    ctx->bulkOutState.transferSize = ctx->bulkOutHeader.cmdSpec.DevDepMsgOut.transferSize;
                    ctx->bulkOutState.bInProgress = true;
                    ctx->bulkOutState.bNewTransfer = true;
                    ctx->bulkOutState.bytesCounter = 0; 
                    ctx->bulkOutState.bEndOfMessage = USBTMC_CHECK_BITMAP( 
                                       ctx->bulkOutHeader.cmdSpec.DevDepMsgOut.bmTransferAttributes,
                                       bm_DEV_DEP_MSG_OUT_EOM );

                    status = tmclib_status_success;
                }
            }
            break;
            case eUsbtmcMsg_ReqDevDepMsgIn:
            {
                if( 0 == ctx->bulkOutHeader.cmdSpec.ReqDevDepMsgIn.transferSize )
                {
                    // error: invalid @transferSize
                    status = tmclib_status_header_error; 
                }
                else
                {
                    if( USBTMC_CHECK_BITMAP(
                           ctx->bulkOutHeader.cmdSpec.ReqDevDepMsgIn.bmTransferAttributes,
                           bm_REQUEST_DEV_DEP_MSG_IN_TRMCH ) )
                    {
                        // error: TermChar is not supported
                        status = tmclib_status_unsupported_termchar;
                    }
                    else
                    {
                        // Initialize BulkIn transfer                        
                        ctx->bulkInState.transferSize = ctx->bulkOutHeader.cmdSpec.ReqDevDepMsgIn.transferSize;
                        ctx->bulkInState.bInProgress = true; 
                        ctx->bulkInState.bNewTransfer = true;
                        ctx->bulkInState.bytesCounter = 0; 
                        ctx->bulkInState.bEndOfMessage = false; // @bEndOfMessage can be changed later

                        // Filling header:
                        ctx->bulkInHeader.bTag = ctx->bulkOutHeader.bTag;
                        ctx->bulkInHeader.bTagInverse = ctx->bulkOutHeader.bTagInverse;
                        ctx->bulkInHeader.MsgID = eUsbtmcMsg_DevDepMsgIn;
                        ctx->bulkInHeader.Reserved = 0;
                        ctx->bulkInHeader.cmdSpec.DevDepMsgIn.transferSize = ctx->bulkInState.transferSize;
                        ctx->bulkInHeader.cmdSpec.DevDepMsgIn.bmTransferAttributes = bm_DEV_DEP_MSG_IN_EOM; // set End-of-message indicator by default

                        status = tmclib_status_success;
                    }
                }
            }
            break;
            case eUsbtmcMsg_VendorSpecificOut:
            {
                 // error: VENDOR_SPECIFIC_OUT message is not supported
                 status = tmclib_status_unsupported_message_error; 
            }
            break;
            case eUsbtmcMsg_ReqVendorSpecificIn:
            {
                 // error: REQUEST_VENDOR_SPECIFIC_IN message is not supported
                 status = tmclib_status_unsupported_message_error; 
            }
            break;
            case eUsbtmcMsg_Usb488_Trigger:
            {
                 // error: Trigger message is not supported
                 status = tmclib_status_unsupported_message_error; 
            }
            break;
            default:
            {
                 // error: Invalid MsgID
                 status = tmclib_status_invalid_message_error; 
            }
       }
   }
   while( false );

   return (lastRequestStatus = status);    
}

// =================================================================================
// @tmclib_new_notification_transfer()
// Starts new InterruptIN transfer
// @ctx - the USBTMC context
// @bTag - the Tag identifier, [2], 3.4.2 Interrupt-IN DATA sent due to READ_STATUS_BYTE request
static eTMCLibStatus_t tmclib_new_notification_transfer( sUSBTMCContext_t * ctx, uint8_t bTag )
{
   eTMCLibStatus_t status = tmclib_status_success;

   do
   {
       if( usb_space_transfer( ctx->interruptInState.transf ) < sizeof( ctx->interruptInHeader ) )
       { 
           // 3.4.2 Interrupt-IN DATA sent due to READ_STATUS_BYTE request, [2]
           // error: no free space to place notification Interrupt-IN packet. Processing is impossible.
           status = tmclib_status_header_fragmented_error;
           break;
       }

       // Initialize InterruptIn transfer                        
       ctx->interruptInState.bTag = bTag;   ///???
       ctx->interruptInState.STB = 0;       ///???
       ctx->interruptInState.bInProgress = true; 
       ctx->interruptInState.bNewTransfer = true;
       ctx->interruptInState.bEndOfMessage = false;
       ctx->interruptInState.bytesCounter = 0;

       // Filling header (can be modified later by @tmclib_update_notification):
       tmclib_cleanup_interruptin_header( ctx );

       status = tmclib_status_success;
   }
   while( false );

   return (lastRequestStatus = status);    
}

// =================================================================================
// @tmclib_bulkout_gettag
// Returns the bTag field for current BulkOut transfer
// @pbTag - pointer to the bTag to be filled, can not be NULL
// Note: if there no transaction is, function returns bTag=0
// Returns: returns @tmclib_status_success if @pbTag is not NULL,
// otherwise returns @tmclib_status_invalid_param
eTMCLibStatus_t tmclib_bulkout_gettag( uint8_t * pbTag )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pbTag )
    {
        return ( lastRequestStatus = tmclib_status_invalid_param );
    }

    if( ctx->bulkOutState.bInProgress )
    {
        *pbTag = ctx->bulkOutHeader.bTag;
    }
    else
    {
        *pbTag = 0;
    }

    return ( lastRequestStatus = tmclib_status_success );
}

// =================================================================================
// @tmclib_bulkin_gettag
// Returns the bTag field for current BulkIn transfer
// @pbTag - pointer to the bTag to be filled, can not be NULL
// Note: if there no transaction is, function returns bTag=0
// Returns: returns @tmclib_status_success if @pbTag is not NULL,
// otherwise returns @tmclib_status_invalid_param
eTMCLibStatus_t tmclib_bulkin_gettag( uint8_t * pbTag )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pbTag )
    {
        return ( lastRequestStatus = tmclib_status_invalid_param );
    }

    if( ctx->bulkInState.bInProgress )
    {
        *pbTag = ctx->bulkInHeader.bTag;
    }
    else
    {
        *pbTag = 0;
    }

    return ( lastRequestStatus = tmclib_status_success );
}

// =================================================================================
// @tmclib_bulkout_gettag_latest
// Returns the bTag field for latest valid BulkOut transfer
// @pbTag - pointer to the bTag to be filled, can not be NULL
// Note: if there no transaction is, function returns bTag for the latest valid 
// BulkOut transfer, or bTag for current transaction otherwise.
// Returns: returns @tmclib_status_success if @pbTag is not NULL,
// otherwise returns @tmclib_status_invalid_param
eTMCLibStatus_t tmclib_bulkout_gettag_latest( uint8_t * pbTag )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pbTag )
    {
        return ( lastRequestStatus = tmclib_status_invalid_param );
    }

    if( ctx->bulkOutState.bInProgress )
    {
        // current transaction
        *pbTag = ctx->bulkOutHeader.bTag;
    }
    else
    {
        if( 0 != ctx->bulkOutState.bLatestTag )
        {
            *pbTag = ctx->bulkOutState.bLatestTag;
        }
        else
        {
            *pbTag = 0;
        }
    }

    return ( lastRequestStatus = tmclib_status_success );
}

// =================================================================================
// @tmclib_bulkin_gettag_latest
// Returns the bTag field for latest valid BulkIn transfer
// @pbTag - pointer to the bTag to be filled, can not be NULL
// Note: if there no transaction is, function returns bTag for the latest valid 
// BulkIn transfer, or bTag for current transaction otherwise.
// Returns: returns @tmclib_status_success if @pbTag is not NULL,
// otherwise returns @tmclib_status_invalid_param
eTMCLibStatus_t tmclib_bulkin_gettag_latest( uint8_t * pbTag )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pbTag )
    {
        return ( lastRequestStatus = tmclib_status_invalid_param );
    }

    if( ctx->bulkInState.bInProgress )
    {
        // current transaction
        *pbTag = ctx->bulkInHeader.bTag;
    }
    else
    {
        if( 0 != ctx->bulkInState.bLatestTag )
        {
            *pbTag = ctx->bulkInState.bLatestTag;
        }
        else
        {
            *pbTag = 0;
        }
    }

    return ( lastRequestStatus = tmclib_status_success );
}

// =================================================================================
// @tmclib_bulkout_getcounter
// Returns the bytes counter for current BulkOut transfer
// @pnBytes - pointer to the uint32_t variable to be filled, can not be NULL
// Note: if there no transaction is, function returns the counter for the last transfer
// Returns: returns @tmclib_status_success if @pnBytes is not NULL,
// otherwise returns @tmclib_status_failure
eTMCLibStatus_t tmclib_bulkout_getcounter( uint32_t * pnBytes )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pnBytes )
    {
        return ( lastRequestStatus = tmclib_status_failure ); 
    }

    *pnBytes = ctx->bulkOutState.bytesCounter;

    return ( lastRequestStatus = tmclib_status_success );
}
// =================================================================================
// @tmclib_bulkin_getcounter
// Returns the bytes counter for current BulkIn transfer
// @pnBytes - pointer to the uint32_t variable to be filled, can not be NULL
// Note: if there no transaction is, function returns the counter for the last transfer
// Returns: returns @tmclib_status_success if @pnBytes is not NULL,
// otherwise returns @tmclib_status_failure
eTMCLibStatus_t tmclib_bulkin_getcounter( uint32_t * pnBytes )
{
    sUSBTMCContext_t * ctx = &ctxUsbTmc;

    if( NULL == pnBytes )
    {
        return ( lastRequestStatus = tmclib_status_failure );
    }

    *pnBytes = ctx->bulkInState.bytesCounter;

    return ( lastRequestStatus = tmclib_status_success );
}

// =================================================================================
// @tmclib_bulkout_check_inprogress()
// Checks weither the Bulk-OUT transfer is in progress
// @ctx - the USBTMC context
// Returns: tmclib_status_in_progress in case the transfer is in progress,
// and tmclib_status_not_in_progress otherwise.
static eTMCLibStatus_t tmclib_bulkout_check_inprogress( sUSBTMCContext_t * ctx )
{
    if( ! ctx->bulkOutState.bInProgress )
    {
        // status: not in progrss
        return (lastRequestStatus = tmclib_status_not_in_progress);
    }

    // status: In progrss
    return (lastRequestStatus = tmclib_status_in_progress);
}

// =================================================================================
// @tmclib_bulkin_check_inprogress()
// Checks weither the Bulk-IN transfer is in progress
// @ctx - the USBTMC context
// Returns: tmclib_status_in_progress in case the transfer is in progress,
// and tmclib_status_not_in_progress otherwise.
static eTMCLibStatus_t tmclib_bulkin_check_inprogress( sUSBTMCContext_t * ctx )
{
    if( ! ctx->bulkInState.bInProgress )
    {
        // status: not in progrss
        return (lastRequestStatus = tmclib_status_not_in_progress);
    }

    // status: In progrss
    return (lastRequestStatus = tmclib_status_in_progress);
}

// =================================================================================
// @tmclib_interruptin_check_inprogress()
// Checks weither the Interrupt-IN transfer is in progress
// @ctx - the USBTMC context
// Returns: tmclib_status_in_progress in case the transfer is in progress,
// and tmclib_status_not_in_progress otherwise.
static eTMCLibStatus_t tmclib_interruptin_check_inprogress( sUSBTMCContext_t * ctx )
{
    if( ! ctx->interruptInState.bInProgress )
    {
        // status: not in progrss
        return (lastRequestStatus = tmclib_status_not_in_progress);
    }

    // status: In progrss
    return (lastRequestStatus = tmclib_status_in_progress);
}

// =================================================================================
// @tmclib_bulkout_event
// Processes USBTMC Bulk-OUT event
// @eventCtx - event context (sTMCBulkOutEventCtx_t part is used)
// @eventCtx->event - event id to process (only eTMCEventIO is supported):
//  * eTMCEventIO - data I/O signal
// @eventCtx->bulkOut.rx - the buffer object to be used to receive data
// Returns: the operation status:
//   tmclib_status_success in case of success,
//   tmclib_status_in_progress in of no error has occurred, but it is required to receive more data,
//   tmclib_status_halt_bulkin in case of error and it is required to halt BulkIN EP,
//   tmclib_status_failure otherwise and it is requried to halt BulkOUT EP.
static eTMCLibStatus_t tmclib_bulkout_event( sTMCEventContext_t * eventCtx )
{
   eTMCLibStatus_t status = tmclib_status_success;
   sUSBTMCContext_t * ctx = &ctxUsbTmc;   
   bool bProcessed = false;

   switch( eventCtx->event )
   {
       case eTMCEventBulkOut:
       {
           // Check if the BulkOut transfer is in progress
           if( tmclib_status_not_in_progress == tmclib_bulkout_check_inprogress( ctx ) )
           {
               // No, the transfer either is aborted or not started yet.
               // create new BulkOut/BulkIn transfer
               status = tmclib_new_data_transfer( ctx, eventCtx->bulkOut.rx );

               // check for BulkIn transfer
               if( tmclib_status_in_progress == tmclib_bulkin_check_inprogress( ctx ) )
               {
                   // new BulkIN transfer just has been created
                   bProcessed = true; // at least one transfer is created

                   // push the BULKIN header into output transfer:
                   if( !tmclib_write_bulkin_header( ctx ) )
                   {
                       status = tmclib_status_failure;
                   }
                   else
                   {
                       // this is to unlock transfer flags and reset short-packet indicator for new transfer
                       usb_transfer_clear_shortpacket( ctx->bulkInState.transf );

                       // perform virtual BulkIN event
                       status = tmclib_bulkin_process( ctx, ctx->bulkInState.transf );

                       // check status
                       if( USBTMC_NOTERROR(status) )
                       {
                           switch(status)
                           {
                              case tmclib_status_in_progress:
                              {
                                  my_assert( status != tmclib_status_in_progress );        // 'tmclib_bulkin_process' never returns 'tmclib_status_in_progress' 
                                  status = tmclib_status_failure; 
                              }
                              break;
                              case tmclib_status_not_in_progress:
                              {
                                  my_assert( status != tmclib_status_not_in_progress );    // 'tmclib_bulkin_process' never returns 'tmclib_status_not_in_progress' 
                                  status = tmclib_status_failure; 
                              }
                              break;
                              case tmclib_status_again:
                              {
                                  my_assert( status != tmclib_status_again );              // 'tmclib_bulkin_process' never returns 'tmclib_status_again' 
                                  status = tmclib_status_failure; 
                              }
                              break;
                              case tmclib_status_need_data:
                              {
                                  my_assert( status != tmclib_status_need_data );          // 'tmclib_bulkin_process' never returns 'tmclib_status_need_data' 
                                  status = tmclib_status_failure; 
                              }
                              break;

                              case tmclib_status_success:
                              case tmclib_status_no_more_data:
                              case tmclib_status_need_read:
                              {
                                  // if BulkIN event succeded, it is required to
                                  // ... send data from output data.
                                  status = tmclib_status_need_read;
                              }
                              break;
                              
                              default:
                                  my_assert( status == tmclib_status_success );            // 'tmclib_bulkin_process' returns undefined code 
                           }
                       }
                   }
               }
           } 
       }
       break;

       default: status = tmclib_status_invalid_param; // invalid event
                my_assert( false );
   }
   
   if( USBTMC_NOTERROR(status) )
   {
       // Yes, continue the transfer processing

       // Check if BulkOut transfer is in progress
       // NOTE: BulkOUT transfer also initiates BulkIN transfers, so,
       // ... event @status is success, the BulkOut transfer can be
       // ... not started due to it just initiates BulkIn transfer.
       if( tmclib_status_in_progress == tmclib_bulkout_check_inprogress( ctx ) )
       {    
           bProcessed = true; // at least one transfer is created

           status = tmclib_bulkout_process( ctx, eventCtx->bulkOut.rx );
       }
   }

   if( !bProcessed )
   {
       if( status == tmclib_status_success )
           status =  tmclib_status_not_in_progress;
   }

   lastRequestStatus = status;

   // check the error code:
   if( USBTMC_ERROR(status) )
   {
       tmclib_bulkout_error( ctx, eventCtx->bulkOut.rx, status );

       tmclib_abort_bulkout( COMPOUND_tmclib_event_context_abort_bulkout(0, true) );

       if( status != tmclib_status_halt_bulkin )
       {
           status = tmclib_status_failure;
       }
   }   

   return ( status );
}

// =================================================================================
// @tmclib_bulkin_event
// Processes USBTMC Bulk-IN event
// @eventCtx - event context (sTMCBulkInEventCtx_t part is used)
// @eventCtx->event - event id to process (only eTMCEventIO is supported):
//  * eTMCEventIO - data I/O signal
// @eventCtx->bulkIn.tx - the buffer object to be used to transmit data
// Returns: the operation status:
//   =tmclib_status_success in case of success,
//   >tmclib_status_success in case of warning,
//   <tmclib_status_success in case of failure
//
// Note: User shall reset Short-Packet indicator using @usb_transfer_modify_flags
// ... routine to avoid sending short packet in case no data queued into the transfer
// ... after successful call (return true with empty transfer)
static eTMCLibStatus_t tmclib_bulkin_event( sTMCEventContext_t * eventCtx )
{
   eTMCLibStatus_t status = tmclib_status_success;
   sUSBTMCContext_t * ctx = &ctxUsbTmc;

   switch( eventCtx->event )
   {
       case eTMCEventBulkIn:
       {
           // Check if the BulkIn transfer is in progress
           if( tmclib_status_in_progress == tmclib_bulkin_check_inprogress( ctx ) )
           {
              // Yes, the transfer is in progress
           
              // continue the transfer processing
              status = tmclib_bulkin_process( ctx, eventCtx->bulkIn.tx );
           }
           else
           {
              // BulkIn transfer is not in progress
              // Possibilities:
              // 1. It is the last packet and it is would be good to clear EP IN
              // 2. It is an erroneous IN-transaction from host
              status = tmclib_status_not_in_progress;
           }
       }
       break;

       default: status = tmclib_status_invalid_param; // invalid event
                my_assert( false );
   }

   lastRequestStatus = status;

   // Do to transform WARNING codes to ERRORs: generate BulkIN error on error codes only
   if( USBTMC_ERROR(status) )
   {
       tmclib_bulkin_error( ctx, eventCtx->bulkIn.tx, status );
   }

   return ( status );
}    

// =================================================================================
// @tmclib_interruptin_event
// Processes USBTMC Interrupt-IN event
// @eventCtx - event context (sTMCInterruptInEventCtx_t part is used)
// @eventCtx->event - event id to process:
//  * eTMCEventInterruptInStart - startup signal to initiate InterruptIn trnasfer, must be generated before eTMCEventIO  
//  * eTMCEventIO - data I/O signal
// @eventCtx->interruptIn.ntx - the buffer object to be used to transmit data, must be NULL on 'eTMCEventInterruptInStart' event
// @eventCtx->interruptIn.bTag - the Tag identifier, [2], 3.4.2 Interrupt-IN DATA sent due to READ_STATUS_BYTE request,
// ... is valid only on 'eTMCEventInterruptInStart' event
//       
// Returns: the operation status:
//   tmclib_status_success in case of success,
//   tmclib_status_failure otherwise
//
// Note: if @initiate is true, the function only creates new transfer without calling Tx-Handler,
// and @ntx must be NULL
//
// Note: Each event generates a single INTERRUPT-IN packet begining with Interrupt-IN header
// Note: Each interrupt-in event must be not longer than @ntx transfer length.
// Note: In case it is required to send more data than @ntx can carry, you need:
//  - increase Interrupt-IN EP buffer (see USB specification) and increase @ntx transfer space
// or:
//  - send the data using multiple packets (each packet begins with INTERRUPT-IN header), pay
// attention that @ctx->interruptInState will not be reset until user layer return tmclib_status_success
// or tmclib_status_no_more_data.
static eTMCLibStatus_t tmclib_interruptin_event( sTMCEventContext_t * eventCtx )
{
   eTMCLibStatus_t status = tmclib_status_not_in_progress;  // by default: transfer is not in progress
   sUSBTMCContext_t * ctx = &ctxUsbTmc;

   switch( eventCtx->event )
   {
       case eTMCEventInterruptInStart:  /* do not process event on start signal */
       {   
           // Only create the transfer on start signal
           if( NULL != eventCtx->interruptIn.ntx )
           {
               // Formal checking: on @initiate=true no @ntx pointer is known
               // Do checking to catch errors.

               // Do not call @tmclib_interruptin_error due to @ntx is must be NULL, but it is not by error
               return tmclib_status_invalid_param;
           }

           // Create new transfer with specified @bTag
           status = tmclib_new_notification_transfer( ctx, eventCtx->interruptIn.bTag );
       }
       break;

       case eTMCEventInterruptIn:
       {
           // check for Interrupt-In transfer
           // Continue only if transfer is already started
           if( tmclib_status_in_progress == tmclib_interruptin_check_inprogress( ctx ) )
           {
               // new InterruptIN transfer just has been created

               // push the INTERRUPT-IN header into output transfer
               // Warning: each event begins with new notification header.
               if( !tmclib_write_notification_header( ctx ) )
               {
                   status = tmclib_status_failure;
               }
               else
               {
                   /* No short packet control is required
                   #if 0
                   // this is to unlock transfer flags and reset short-packet indicator for new transfer
                   usb_transfer_clear_shortpacket( ntx );
                   #endif
                   */

                   // perform virtual InterruptIN event
                   status = tmclib_interruptin_process( ctx, eventCtx->interruptIn.ntx );

                   // check status
                   if( USBTMC_NOTERROR(status) )
                   {
                       switch(status)
                       {
                          case tmclib_status_in_progress:
                          {
                              my_assert( status != tmclib_status_in_progress );        // 'tmclib_interruptin_process' never returns 'tmclib_status_in_progress' 
                              status = tmclib_status_failure; 
                          }
                          break;
                          case tmclib_status_not_in_progress:
                          {
                              my_assert( status != tmclib_status_not_in_progress );    // 'tmclib_interruptin_process' never returns 'tmclib_status_not_in_progress' 
                              status = tmclib_status_failure; 
                          }
                          break;
                          case tmclib_status_again:
                          {
                              my_assert( status != tmclib_status_again );              // 'tmclib_interruptin_process' never returns 'tmclib_status_again' 
                              status = tmclib_status_failure; 
                          }
                          break;
                          case tmclib_status_need_data:
                          {
                              my_assert( status != tmclib_status_need_data );          // 'tmclib_interruptin_process' never returns 'tmclib_status_need_data' 
                              status = tmclib_status_failure; 
                          }
                          break;

                          case tmclib_status_success:
                          case tmclib_status_no_more_data:
                          {
                               // Attention: complete transfer but do not interrupt (do not clear usb_transfer) !
                               // since no more data is planned to send: abort interrupt-in transfer
                               status = tmclib_complete_interruptin( ctx->interruptInState.bTag, false );
                          }
                          /*no break*/
                          case tmclib_status_need_read:
                          {
                              // if BulkIN event succeded, it is required to
                              // ... send data from output data.
                              status = tmclib_status_need_read;
                          }
                          break;
                          
                          default:
                              my_assert( status == tmclib_status_success );            // 'tmclib_interruptin_process' returns undefined code 
                       }
                   }
               }
           }
       }
       break;

       default: status = tmclib_status_invalid_param; // invalid event
                my_assert( false );
   }

   lastRequestStatus = status;

   // check the error code:
   if( USBTMC_ERROR(status) )
   {
       tmclib_interruptin_error( ctx, eventCtx->interruptIn.ntx, status );

       tmclib_abort_interruptin( COMPOUND_tmclib_event_context_abort_interruptin(0, true) );

       status = tmclib_status_failure;
   }   

   return status;
}

// =================================================================================
// @tmclib_bulkout_process
//
static eTMCLibStatus_t tmclib_bulkout_process( sUSBTMCContext_t * ctx, sUSBTransfer_t * rx )
{
   // Bytes left in the @rx transfer
   size_t nBytes = usb_count_transfer( rx );

   // Bytes left to receive during current USBTMC transfer
   size_t nBytesLeft = ctx->bulkOutState.transferSize - ctx->bulkOutState.bytesCounter;
   
   // Alignment bytes requried to receive
   size_t nBytesAlignment = (sizeof(uint32_t) - ctx->bulkOutState.transferSize % sizeof(uint32_t)) % sizeof(uint32_t);

   // retrieve the short packet flag
   bool bShortPacket = usb_transfer_check_shortpacket( rx ); // bulk-out: short packet received?

   // Error identifier: reset
   int32_t nError = SCPILIB_ERROR_SUCCESS;

   // new transfer indicator show if a new transfer started since this packet
   bool bNewTransfer = ctx->bulkOutState.bNewTransfer;

   // Check if the last packet is a short packet (no more data is expected after the short packet)
   if( bShortPacket )
   {
       // If the short packet has been received, the transfer is running out.
       // This is the last BulkOut event in the transfer.
       // Check if the read data counters are match to the expected:
       // UPD. Host can join DEV_DEP_MSG_OUT and REQUEST_DEV_DEP_MSG_IN, and @rx will contain
       // more data than the transfer declares.
       if( nBytes < nBytesLeft + nBytesAlignment )
       {           
           nBytes = 0;
           nError = SCPILIB_ERROR_BUFFER_OVERFLOW;
       }
       else
       {
           // if a short packet received then pass @nBytesLeft instead of @nBytes
           nBytes = nBytesLeft; // cut of alignment bytes  
       }
   }
   else
   {   
       // Calculate whole transfer size
       size_t bytesRequied = (sizeof( ctx->bulkOutHeader ) + nBytesLeft + nBytesAlignment);

       // Since USBTMC Library uses Low-Level USB transfer buffers, it is required
       // the USB Bulk OUT transfer to be not frangmented, so it must fit to the
       // transfer @rx, check it:
       if( usb_size_transfer( rx ) <= bytesRequied )
       {
           // overflow
           (void)nBytes;
           nError = SCPILIB_ERROR_BUFFER_OVERFLOW;
       }
   }

   // check error condition
   if( SCPILIB_ERROR_SUCCESS == nError )
   {
       if( bNewTransfer )
       {
           // Since new transfer started it is required to restart SCPI-parser
           sSCPILibHandle.fNewTransfer();
       }
       
       switch( sSCPILibHandle.fWrite( usb_transfer_raw_read( rx ),  // get raw pointer and pass it to @fWrite
                                      nBytes, bShortPacket, bNewTransfer ) )
       {
           case eScpiStatus_success:  // Both either failed or success statuses means
           case eScpiStatus_failed:   // ... that the SCPI library has been processed request.
           {                          // Need to return @tmclib_status_success to not halt
                                      // ... Bulk-OUT endpoint.

                // @nBytes bytes have been processed            
                
                lastRequestStatus = tmclib_status_success;   // request processed
           }
           break;

           case eScpiStatus_invalid:
           {
                // @nBytes bytes have been processed            

                lastRequestStatus = tmclib_status_failure;   // development error
           }
           break;

           case eScpiStatus_need_data:
           {
                if( bShortPacket )
                {
                    lastRequestStatus = tmclib_status_failure;  // development error
                }
                else
                {
                    // Need more data to continue, it is required to keep
                    // ... @nBytes bytes in the buffer til the next call.
                    nBytes = 0; // clear @nBytes to keep data in the buffer

                    lastRequestStatus = tmclib_status_need_data;
                }
           }
           break;
       }

       // Retrieve bytes from the buffer
       if( 0 < nBytes )
       {
           // @nBytes bytes have been processed
           usb_transfer_virtual_read( rx, nBytes );
           
           nBytesLeft -= nBytes;
     
           // Check if all the protocol data has been processed (except alignment data)
           if( 0 == nBytesLeft )
           {
               if( 0 < nBytesAlignment )
               {
                   usb_transfer_virtual_read( rx, nBytesAlignment );
               } 
           }
       }

       if( bShortPacket )
       {
           // Since the short packet received the transfer must be terminated

           // Check if there data left in the transfer.
           // If it is, check if there no error occurred?
           if( USBTMC_NOTERROR(lastRequestStatus) && (usb_count_transfer( rx ) > 0) )
           {
               // Yes, there some data left, and no error occurred
               // Maybe it is another USBTMC header?
               tmclib_abort_bulkout( COMPOUND_tmclib_event_context_abort_bulkout_noreset(0, true) );

               // Notify the caller to call the routine again to process the rest data in the transfer
               lastRequestStatus = tmclib_status_again;
           }
           else
           {
               // No data or error occurred, just reset transfer
               tmclib_abort_bulkout( COMPOUND_tmclib_event_context_abort_bulkout(0, true) );
           }
           
       }

       // reset 'new transfer' indicator
       ctx->bulkOutState.bNewTransfer = false;
   }
   else
   {
       // Error condition.

       // Notify the SCPI level
       switch( sSCPILibHandle.fError( nError, NULL ) )
       {
           case eScpiStatus_success:
               lastRequestStatus = tmclib_status_success; // error processed
           break;

           case eScpiStatus_failed:    // Unrecoverable error
           case eScpiStatus_invalid:
                lastRequestStatus = tmclib_status_failure;
           break;
           
           case eScpiStatus_need_data: // Forbidden states
           default:
                my_assert( false );
                lastRequestStatus = tmclib_status_failure;
       }

       // reset transfer
       tmclib_abort_bulkout( COMPOUND_tmclib_event_context_abort_bulkout(0, true) ); 
   }

   // 3.2 Bulk-OUT endpoint, [1]
   // 3.2.2.3 Bulk-OUT transfer protocol errors, [1]
   return ( lastRequestStatus );
}

// =================================================================================
// tmclib_bulkin_process()
// Processes BulkIN transfer.
//
// Note: User shall reset Short-Packet indicator using @usb_transfer_modify_flags
// ... routine to avoid sending short packet in case no data queued into the transfer
// ... after successful call (return true with empty transfer)
static eTMCLibStatus_t tmclib_bulkin_process( sUSBTMCContext_t * ctx, sUSBTransfer_t * tx )
{
   // Bytes free in the @tx transfer
   size_t nBytes = usb_space_transfer( tx );
   size_t nBytesOut = 0;

   // Due to USBTMC required to send data by portions with
   // ... the length multiple of 4, it is required to limit the
   // ... buffers size up to the maximum length multiple of 4 to
   // ... avoid the case when it is there no free space in the 
   // ... buffer to add alinment bytes in the end.
   nBytes -= nBytes%sizeof(uint32_t);

#warning SCPI freeSpace+sentCount ?????
   // Check the transfer boundary: @bytesCounter (sent) + @nBytes (available space) 
   if( nBytes + ctx->bulkInState.bytesCounter >
                ctx->bulkInState.transferSize )
   {
       // limit the buffer space
       // TransferSize does not include the number of bytes in BulkIN header or alignment bytes
       nBytes = ctx->bulkInState.transferSize - ctx->bulkInState.bytesCounter;
   }

   // check if current transfer ran out:
   // check if all the data is already queued in transfer:
   if( 0 == nBytes )
   {
       // set success status without queuing any data
       lastRequestStatus = tmclib_status_success;

       // check if all the data is already send from transfer:
       if( 0 == usb_count_transfer( tx ) )
       {   
           // close Bulk-IN transfer due to there no more data to send
           // Do not clear short packet indicator (use COMPOUND_tmclib_event_context_complete_bulkin instead of COMPOUND_tmclib_event_context_abort_bulkin!)
           tmclib_abort_bulkin( COMPOUND_tmclib_event_context_complete_bulkin(ctx->bulkInHeader.bTag, false) ); 

           // set informative status: equal to 'tmclib_status_success'
           lastRequestStatus = tmclib_status_no_more_data;
       }

       // do not queue any data
       nBytesOut = 0;
   }
   else
   {
       bool EOM = true;  // forward set

       // generate SCPI-parser read-event
       switch( sSCPILibHandle.fRead( usb_transfer_raw_write( tx ),  // get raw pointer and pass it to @fRead
                                     nBytes, &nBytesOut, &EOM ) ) 
#warning SCPI write more than TransferSize due to sw buffer (@tx) is larger than TransferSize
       {
           case eScpiStatus_failed:    
           {
                nBytesOut = 0;  // due to error: clear @nBytesOut

                // // SCPI library has been failed during the request processing.
                // Need to return @eScpiStatus_failed to halt Bulk-OUT endpoint.
                tmclib_abort_bulkin( COMPOUND_tmclib_event_context_abort_bulkin(ctx->bulkInHeader.bTag, false) );

                // this is to unlock transfer flags only
                usb_transfer_clear_shortpacket( tx );

                lastRequestStatus = tmclib_status_read_error;
           }
           break;

           case eScpiStatus_success:  
           {                          
                // // SCPI library has been successfully processed the request.
                // Need to return @tmclib_status_success to not halt Bulk-OUT endpoint.

                // @EOM: is true if there no data is planned to transfer, it is required
                // ... to indicate that this message is the last one in current transfer;
                // @EOM: is false if it is required to generate another READ event to indicate 
                // that this message is NOT the last one in current transfer;
                ctx->bulkInState.bEndOfMessage = EOM; // set End-of-message indicator

                if( 0 < nBytesOut )
                {
                    // @nBytesOut bytes have been placed into the output buffer            
                    ctx->bulkInState.bytesCounter += nBytesOut;

#if DEBUG_USBTMC > 0 // SCPI debug only
                    gDebugUSBTMC_TxBytes += nBytesOut;
#endif

                    // since it is the last transaction, it is required adjust 
                    // ... the ending data boundary to fit the 32-bit alignment
                    // ... using the entire transfer bytes counter @bytesCounter: 
                    nBytesOut += sizeof(uint32_t) - ctx->bulkInState.bytesCounter%sizeof(uint32_t);
                }

                // update current transfer size: set to current transfer counter
                ctx->bulkInState.transferSize = ctx->bulkInState.bytesCounter;

                // update bulk-in header in current buffer
                tmclib_update_bulkin( ctx );
                
                lastRequestStatus = tmclib_status_success;   // request processed
           }
           break;

           case eScpiStatus_invalid:
           {
                // @nBytes bytes have been processed            

                lastRequestStatus = tmclib_status_failure;   // development error
           }
           break;

           case eScpiStatus_need_data:
           {            
                // Need more free space to continue, it is required to
                // ... send @nBytesOut bytes from output buffer.            
                ctx->bulkInState.bytesCounter += nBytesOut;

#if DEBUG_USBTMC > 0 // SCPI debug only
                gDebugUSBTMC_TxBytes += nBytesOut;
#endif

                // since more data is planned to transfer, it is required
                // ... to indicate that this message is not the last one in
                // ... current transfer:
                ctx->bulkInState.bEndOfMessage = false; // reset End-of-message indicator

                // update current transfer size: set to current transfer counter
                ctx->bulkInState.transferSize = ctx->bulkInState.bytesCounter;

                // update bulk-in header in current buffer
                tmclib_update_bulkin( ctx );

                // set status
                lastRequestStatus = tmclib_status_need_read;
           }
           break;
       }
   }
   // perform virtual write (the data is already in the buffer)
   usb_transfer_virtual_write( tx, nBytesOut );
           
   // reset 'new transfer' indicator
   ctx->bulkInState.bNewTransfer = false;

   return lastRequestStatus; 
}
// =================================================================================
// tmclib_interruptin_process()
// Processes InterruptIN transfer.
//
static eTMCLibStatus_t tmclib_interruptin_process( sUSBTMCContext_t * ctx, sUSBTransfer_t * ntx )
{
   // Bytes free in the @tx transfer
   size_t nBytes = usb_space_transfer( ntx );
   size_t nBytesOut = 0;

   // check if current transfer ran out:
   // check if all the data is already queued in transfer:
   if( 0 == nBytes )
   {
       // set success status without queuing any data
       lastRequestStatus = tmclib_status_success;

       // check if all the data is already send from transfer:
       if( 0 == usb_count_transfer( ntx ) )
       {   
           // close Bulk-IN transfer due to there no more data to send
           tmclib_abort_interruptin( COMPOUND_tmclib_event_context_abort_interruptin(ctx->interruptInState.bTag, false) ); 

           // set informative status: equal to 'tmclib_status_success'
           lastRequestStatus = tmclib_status_no_more_data;
       }

       // do not queue any data
       nBytesOut = 0;
   }
   else
   {
       // generate SCPI-parser read-event
       switch( sSCPILibHandle.fNotificationRead( usb_transfer_raw_write( ntx ),  // get raw pointer and pass it to @fRead
                                     nBytes, &nBytesOut, &ctx->interruptInState.STB ) ) 
       {
           case eScpiStatus_failed:    
           {
                nBytesOut = 0;  // due to error: clear @nBytesOut

                // SCPI library has been failed during the request processing.
                // Need to return @eScpiStatus_failed to halt Interrupt-IN endpoint.
                tmclib_abort_interruptin( COMPOUND_tmclib_event_context_abort_interruptin(ctx->interruptInState.bTag, false) );

                // this is to unlock transfer flags only
                usb_transfer_clear_shortpacket( ntx );

                lastRequestStatus = tmclib_status_read_error;
           }
           break;

           case eScpiStatus_success:  
           {                          
                // // SCPI library has been successfully processed the request.
                // Need to return @tmclib_status_success to not halt endpoint.

                // since no data is planned to transfer, it is required
                // ... to indicate that this message is the last one in
                // ... current transfer:
                ctx->interruptInState.bEndOfMessage = true; // set End-of-message indicator

                if( 0 < nBytesOut )
                {
                    // @nBytesOut bytes have been placed into the output buffer            
                    ctx->interruptInState.bytesCounter += nBytesOut;
                }

                // update bulk-in header in current buffer
                tmclib_update_notification( ctx );
                
                lastRequestStatus = tmclib_status_success;   // request processed
           }
           break;

           case eScpiStatus_invalid:
           {
                // @nBytes bytes have been processed            

                lastRequestStatus = tmclib_status_failure;   // development error
           }
           break;

           case eScpiStatus_need_data:
           {            
                // Need more free space to continue, it is required to
                // ... send @nBytesOut bytes from output buffer.            
                ctx->interruptInState.bytesCounter += nBytesOut;

                // since more data is planned to transfer, it is required
                // ... to indicate that this message is not the last one in
                // ... current transfer:
                ctx->interruptInState.bEndOfMessage = false; // reset End-of-message indicator

                // update bulk-in header in current buffer
                tmclib_update_notification( ctx );

                // set status
                lastRequestStatus = tmclib_status_need_read;
           }
           break;
       }
   }
   // perform virtual write (the data is already in the buffer)
   usb_transfer_virtual_write( ntx, nBytesOut );
           
   // reset 'new transfer' indicator
   ctx->interruptInState.bNewTransfer = false;

   return lastRequestStatus; 

}
// =================================================================================
// @tmclib_device_clear
// Resets the USBTMC and SCPI state
// @eventCtx - event context (sTMCDeviceClearEventCtx_t)
// @eventCtx->event - event id to process:
//  * eTMCEventDeviceClear - resets the USBTMC and SCPI state
// @eventCtx->clear.resetTransport - reset all the transfers
//       
// Returns: the operation status:
//   tmclib_status_success in case of success,
//   tmclib_status_failure otherwise
//
static eTMCLibStatus_t tmclib_device_clear( sTMCEventContext_t * eventCtx )
{
   if( eventCtx->clear.resetTransport )
   {
       eventCtx->abort.transfer_bTag = 0;
       eventCtx->abort.clearShortPacketIndicator = true;
       eventCtx->abort.force = true;
       eventCtx->abort.noResetTransfer = false;

       tmclib_abort_bulkin( eventCtx );
       tmclib_abort_bulkout( eventCtx );
       tmclib_abort_interruptin( eventCtx );
   }

   // Reset SCPI Parser and FSM state
   if( eScpiStatus_success == sSCPILibHandle.fReset() )
   {
       return tmclib_status_success;
   }

   return tmclib_status_failure;
} 

// =================================================================================
static void tmclib_bulkout_error( sUSBTMCContext_t * ctx, sUSBTransfer_t * rx, eTMCLibStatus_t status )
{
   
}

// =================================================================================
static void tmclib_bulkin_error( sUSBTMCContext_t * ctx, sUSBTransfer_t * tx, eTMCLibStatus_t status )
{
}

// =================================================================================
static void tmclib_interruptin_error( sUSBTMCContext_t * ctx, sUSBTransfer_t * tx, eTMCLibStatus_t status )
{
}

// =================================================================================
// @tmclib_generic_event()
// Processes USBTMC specific event.
// @eventCtx - event context, all the fields must be filled in accordance to @eventCtx->event code
// @eventCtx->event - event identifier:
//
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventBulkInStop - Aborts currently running BulkIN transfer
//  |---> @eventCtx->abort:
//  |       - @transfer_bTag - The bTag value associated with the transfer to abort, see 4.2.1.4 [1]
//  |       - @force - makes to ignore the @transfer_bTag if true
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventBulkOutStop - Aborts currently running BulkOUT transfer
//  |---> @eventCtx->abort:
//  |       - @transfer_bTag - The bTag value associated with the transfer to abort, see 4.2.1.2 [1]
//  |       - @force - makes to ignore the @transfer_bTag if true
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventInterruptInStop - Aborts currently running InterruptIN transfer
//  |---> @eventCtx->abort:
//  |       - @transfer_bTag - The bTag value associated with the transfer to abort, see 4.3.1 [2]
//  |       - @force - makes to ignore the @transfer_bTag if true
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventBulkIn - Processes USBTMC Bulk-IN event
//  |---> @eventCtx->bulkIn:
//  |       - @tx - the buffer object to be used to transmit data
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventBulkOut - Processes USBTMC Bulk-OUT event
//  |---> @eventCtx->bulkOut:
//  |       - @rx - the buffer object to be used to receive data
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventInterruptInStart - Initialize new Interrupt-IN transfer w/o data processing    
//  | ...The function only creates new transfer without calling Tx-Handler. This call must be
//  | ...provided before 'eTMCEventInterruptIn' event to prapare transfer.
//  |----> @eventCtx->interruptIn:
//  |        - @ntx - must be NULL, or 'tmclib_status_invalid_param' will be returned otherwise
//  |        - @bTag - the Tag identifier, [2], 3.4.2 Interrupt-IN DATA sent due to READ_STATUS_BYTE request,
//  +------------------------------------------------------------------------------------------------------------
//  * eTMCEventInterruptIn - Processes USBTMC Interrupt-IN event.
//  | Note that 'eTMCEventInterruptInStart' must be generated before 'eTMCEventInterruptIn' for normal operation.
//  | Note: Each event generates a single INTERRUPT-IN packet begining with Interrupt-IN header
//  | Note: Each interrupt-in event must be not longer than @ntx transfer length.
//  | Note: In case it is required to send more data than @ntx can carry, you need:
//  |  - increase Interrupt-IN EP buffer (see USB specification) and increase @ntx transfer space
//  | or:
//  |  - send the data using multiple packets (each packet begins with INTERRUPT-IN header), pay
//  | attention that @ctx->interruptInState will not be reset until user layer return tmclib_status_success
//  | or tmclib_status_no_more_data.
//  |----> @eventCtx->interruptIn:
//  |        - @ntx - the buffer object to be used to transmit data
//  |        - @bTag - don't care
//  +------------------------------------------------------------------------------------------------------------
//       
// Returns: the operation status:
//   tmclib_status_success in case of success,
//   tmclib_status_failure otherwise
//
eTMCLibStatus_t tmclib_generic_event( sTMCEventContext_t * eventCtx )
{
   switch( eventCtx->event )
   {
        case eTMCEventInterruptInStart:  // InterruptIn initialize (only for Interrupt-IN)
        return tmclib_interruptin_event( eventCtx );

        case eTMCEventBulkIn:            // BulkIn event
        return tmclib_bulkin_event( eventCtx );

        case eTMCEventBulkOut:           // BulkOut event
        return tmclib_bulkout_event( eventCtx );

        case eTMCEventInterruptIn:       // InterruptIn event 
        return tmclib_interruptin_event( eventCtx );

        case eTMCEventBulkInStop:        // BulkIn abort
        return tmclib_abort_bulkin( eventCtx );

        case eTMCEventBulkOutStop:       // BulkOut abort
        return tmclib_abort_bulkout( eventCtx );

        case eTMCEventInterruptInStop:   // InterruptIn abort
        return tmclib_abort_interruptin( eventCtx );

        case eTMCEventDeviceClear:       // Device Clear
        return tmclib_device_clear( eventCtx );
   }

   return tmclib_status_invalid_param;
}