/**
 * Copyright (c) 2016 - 2021, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "sdk_common.h"
#if NRF_MODULE_ENABLED(APP_USBD)

#include "app_usbd_core.h"
#include "app_usbd.h"
#include "app_usbd_request.h"
#include "app_usbd_string_desc.h"
#include "nrf.h"
#include "nrf_atomic.h"
#include "app_util_platform.h"
#include "app_usbd.h"
#include "app_usbd_class_base.h"

#define NRF_LOG_MODULE_NAME app_usbd_core

#if APP_USBD_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       APP_USBD_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  APP_USBD_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR APP_USBD_CONFIG_DEBUG_COLOR
#else //APP_USBD_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       0
#endif //APP_USBD_CONFIG_LOG_ENABLED
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

/* Test if VID was configured */
#ifndef APP_USBD_VID
#error APP_USBD_VID not properly defined.
#endif


/* Device version checking */
#if defined(APP_USBD_DEVICE_VER_MAJOR) && defined(APP_USBD_DEVICE_VER_MINOR)
    #if ((APP_USBD_DEVICE_VER_MAJOR)) > 99 || ((APP_USBD_DEVICE_VER_MINOR) > 99)
        #error Major and minor device version value have to be limited to 99.
    #endif
#else
    #error The definition of a pair APP_USBD_DEVICE_VER_MAJOR and APP_USBD_DEVICE_VER_MINOR required.
#endif

/**
 * @internal
 * @defgroup app_usbd_core_internals USB Device high level library core module internals
 * @ingroup app_usbd_core
 *
 * Internal variables, auxiliary macros and functions of USBD high level core module.
 * @{
 */

/** @brief Make USB power value */
#define APP_USBD_POWER_MAKE(ma) (((ma) + 1) / 2)

/**
 @brief Default device descriptor initializer @ref app_usbd_descriptor_device_t
* */
#define APP_USBD_CORE_DEVICE_DESCRIPTOR  {                                                               \
   .bLength = sizeof(app_usbd_descriptor_device_t),    /* descriptor size */                             \
   .bDescriptorType = APP_USBD_DESCRIPTOR_DEVICE,      /* descriptor type */                             \
   .bcdUSB = APP_USBD_BCD_VER_MAKE(2,0,0),             /* USB BCD version: 2.0 */                        \
   .bDeviceClass = 0,                                  /* device class: 0 - specified by interface */    \
   .bDeviceSubClass = 0,                               /* device subclass: 0 - specified by interface */ \
   .bDeviceProtocol = 0,                               /* device protocol: 0 - specified by interface */ \
   .bMaxPacketSize0 = NRF_DRV_USBD_EPSIZE,             /* endpoint size: fixed to: NRF_DRV_USBD_EPSIZE*/ \
   .idVendor = APP_USBD_VID,                           /* Vendor ID*/                                    \
   .idProduct = APP_USBD_PID,                          /* Product ID*/                                   \
   .bcdDevice = APP_USBD_BCD_VER_MAKE(                 /* Device version BCD */                          \
       APP_USBD_DEVICE_VER_MAJOR,                                                                        \
       APP_USBD_DEVICE_VER_MINOR,                                                                        \
       APP_USBD_DEVICE_VER_SUB),                                                                         \
   .iManufacturer = APP_USBD_STRING_ID_MANUFACTURER,   /* String ID: manufacturer */                     \
   .iProduct = APP_USBD_STRING_ID_PRODUCT,             /* String ID: product */                          \
   .iSerialNumber = APP_USBD_STRING_ID_SERIAL,         /* String ID: serial */                           \
   .bNumConfigurations = 1                             /* Fixed value: only one configuration supported*/\
}


#define APP_USBD_CORE_CONFIGURATION_DESCRIPTOR {                                        \
    .bLength = sizeof(app_usbd_descriptor_configuration_t),                             \
    .bDescriptorType = APP_USBD_DESCRIPTOR_CONFIGURATION,                               \
    .wTotalLength = 0,          /*Calculated dynamically*/                              \
    .bNumInterfaces = 0,        /*Calculated dynamically*/                              \
    .bConfigurationValue = 1,   /*Value passed to set configuration*/                   \
    .iConfiguration = APP_USBD_STRING_ID_CONFIGURATION,        /*Configuration ID*/     \
    .bmAttributes = APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_ALWAYS_SET_MASK |       \
                    ((APP_USBD_CONFIG_SELF_POWERED) ?                                   \
                        APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_SELF_POWERED_MASK   \
                        :                                                               \
                        0),                                                             \
    .bMaxPower = APP_USBD_POWER_MAKE(APP_USBD_CONFIG_MAX_POWER),                        \
}

/**
 * @brief Device descriptor instance.
 *
 * @note
 * Constant part of the device descriptor.
 * Values that must be calculated are updated directly in the buffer
 * just before the transmission.
 */
static const app_usbd_descriptor_device_t m_device_dsc =
    APP_USBD_CORE_DEVICE_DESCRIPTOR;

/**
 * @brief Configuration descriptor instance.
 *
 * @note
 * Constant part of the device descriptor.
 * Values that must be calculated are updated directly in the buffer
 * just before the transmission.
 */
static const app_usbd_descriptor_configuration_t m_configuration_dsc =
    APP_USBD_CORE_CONFIGURATION_DESCRIPTOR;

/* Required early declaration of event handler function */
static ret_code_t app_usbd_core_event_handler(app_usbd_class_inst_t const * const  p_inst,
                                              app_usbd_complex_evt_t const * const p_event);

/**
 * @brief Current USB device state.
 *
 * This variable is updated automatically by core library.
 */
static app_usbd_state_t m_app_usbd_state = APP_USBD_STATE_Disabled;

/**
 * @brief Active device features.
 *
 * @note Only @ref APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP is supported for device.
 */
static uint8_t m_device_features_state;

/**
 * @brief Remote wake-up pending flag.
 */
static nrf_atomic_flag_t m_rwu_pending;


/**
 * @brief Core class methods.
 *
 * Base methods interface for core class.
 * This is quite specific class - it would be only connected into endpoint 0.
 * Not connected into instances list.
 */
static const app_usbd_class_methods_t m_core_methods = {
        .event_handler = app_usbd_core_event_handler,
        .feed_descriptors = NULL,
};

/**
 * @brief Setup transfer buffer.
 */
static uint8_t m_setup_transfer_buff[NRF_DRV_USBD_EPSIZE];


/**
 * @brief Handler for outgoing setup data.
 *
 */
static app_usbd_core_setup_data_handler_desc_t m_ep0_handler_desc;

#define APP_USBD_CORE_CLASS_INSTANCE_CONFIG ()


/*lint -u -save -e26 -e40 -e64 -e123 -e505 -e651*/

/**
 * @brief Core instance.
 *
 * Create instance that would be connected into endpoints in USBD library.
 */
APP_USBD_CLASS_INST_GLOBAL_DEF(
    app_usbd_core_inst,
    app_usbd_core,
    &m_core_methods,
    APP_USBD_CORE_CLASS_CONFIGURATION,
    () );
/*lint -restore*/

/**
 * @brief Set the new USB state.
 *
 * Function changes the internal status of the bus.
 * If the bus status is different than the one configured, an event is passed to all
 * the instances.
 *
 * @param state New state to be set.
 *
 * @sa usbd_core_state_get
 */
static void usbd_core_state_set(app_usbd_state_t state)
{
    if (m_app_usbd_state != state)
    {
        m_app_usbd_state = state;
        if(state != APP_USBD_STATE_Configured)
        {
            CLR_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP);
        }
        static const app_usbd_evt_t evt_data = {
            .type = APP_USBD_EVT_STATE_CHANGED
        };
        app_usbd_event_execute((app_usbd_internal_evt_t const *)&evt_data);
    }
}

/**
 * @brief Get the current USB state - internal function.
 *
 * This is just a wrapper for @ref app_usbd_core_state_get
 * to make symmetrical function to the internal @ref usbd_core_state_set.
 *
 * @return Current USB state.
 *
 * @sa usbd_core_state_set
 * @sa app_usbd_core_state_get
 */
static inline app_usbd_state_t usbd_core_state_get(void)
{
    return m_app_usbd_state;
}

/**
 * @brief Check current USBD power connection status.
 *
 */
static inline bool usbd_core_power_is_detected(void)
{
    return 0 != ( (NRF_POWER->USBREGSTATUS) & POWER_USBREGSTATUS_VBUSDETECT_Msk);
}

/**
 * @brief Clear current EP0 handler.
 *
 * Function just clears the EP0 handler without calling it.
 */
static inline void usbd_core_ep0_handler_clear(void)
{
    m_ep0_handler_desc.handler = NULL;
}

/**
 * @brief Safely call EP0 handler.
 *
 * Function calls EP0 handler only if its pointer is non-zero.
 *
 * @param status Status to send as a handler parameter.
 */
static inline ret_code_t usbd_core_ep0_handler_call_and_clear(nrf_drv_usbd_ep_status_t status)
{
    app_usbd_core_setup_data_handler_t handler = m_ep0_handler_desc.handler;
    if (NULL != handler)
    {
        usbd_core_ep0_handler_clear();
        return handler(status, m_ep0_handler_desc.p_context);
    }

    return NRF_ERROR_NULL;
}

/**
 * @brief Check if EP0 handler is configured.
 *
 * EP0 handler is configured is any instance that has processed SETUP command
 * expects some incoming / outgoing data.
 *
 * EP0 handler should be cleared automatically just before it is called
 * (see @ref usbd_core_ep0_handler_call_and_clear).
 * If instance requires more data - it has to setup EP0 handler once more time
 * (see @ref app_usbd_core_setup_data_handler_set).
 *
 * This function adds small layer of abstraction for checking if EP0 handler
 * is already configured.
 *
 * @retval true  EP0 handler is set.
 * @retval false EP0 handler is cleared.
 */
static inline bool usb_core_ep0_handler_check(void)
{
    return (NULL != m_ep0_handler_desc.handler);
}

/**
 * @brief Empty data handler.
 *
 * Data handler used only to mark that there is requested data during SETUP.
 *
 * @return Always NRF_SUCCESS
 * @sa setup_empty_data_handler_desc
 */
static ret_code_t setup_data_handler_empty(nrf_drv_usbd_ep_status_t status, void * p_contex)
{
    UNUSED_PARAMETER(status);
    UNUSED_PARAMETER(p_contex);
    return NRF_SUCCESS;
}

/**
 * @brief
 *
 * Empty EP0 transfer transfer handler.
 */
static app_usbd_core_setup_data_handler_desc_t const m_setup_data_handler_empty_desc =
{
    .handler = setup_data_handler_empty,
    .p_context = NULL
};

/**
 * @brief Structure used as a context for descriptor feeder.
 *
 * Structure with all the data required to process instances to generate descriptor
 * data chunk.
 */
typedef struct
{
    app_usbd_class_inst_t const * p_cinst;          //!< The class instance that is to be processed next.
    const uint8_t * p_desc;                         //!< Pointer at current descriptor or NULL if finished.
                                                    /**<
                                                      * If the value passed by @ref p_desc is NULL on transfer function enter it means that ZLP is required.
                                                      * Or it is time to finish the transfer (depending on @c total_left).
                                                      */
    size_t desc_left;                               //!< Number of bytes left in the current class descriptor to send
    size_t total_left;                              //!< Number of bytes left that was requested by the host
    app_usbd_class_descriptor_ctx_t feed_thread;    //!< Class descriptor context
} app_usbd_core_descriptor_conf_feed_data_t;

/**
 * @brief Default data used by the feeder.
 *
 *
 */
static app_usbd_core_descriptor_conf_feed_data_t m_descriptor_conf_feed_data;

/**
 * @brief Descriptor feeder.
 *
 * Descriptor feeder is used as an callback function when descriptors are
 * transfered and buffer is ready for next data.
 * It prepares next chunk of data to be sent.
 *
 * @param p_next    See @ref nrf_drv_usbd_next_transfer_handler_t documentation.
 * @param p_context Pointer to @ref app_usbd_core_descriptor_feed_data_t data type.
 * @param ep_size   The size of the endpoint.
 *
 * @return See @ref nrf_drv_usbd_next_transfer_handler_t documentation.
 */
static bool usbd_descriptor_conf_feeder(
    nrf_drv_usbd_ep_transfer_t * p_next,
    void * p_context,
    size_t ep_size)
{
    bool continue_req = true;

    app_usbd_core_descriptor_conf_feed_data_t * p_data = p_context;


    if ((p_data->p_desc == NULL) && (app_usbd_class_next_get(p_data->p_cinst) == NULL)
        && (p_data->desc_left == 0))
    {
        /* ZLP */
        continue_req      = false;
        p_next->p_data.tx = NULL;
        p_next->size      = 0;
    }
    else
    {
        ASSERT(ep_size <= NRF_DRV_USBD_FEEDER_BUFFER_SIZE);
        uint8_t * p_tx_buff;
        size_t size = 0; /* Currently added number of bytes */
        size_t tx_size;  /* Number of bytes to send right now */
        bool feeding = false;

        /* Feeder function can use the USBD driver internal buffer */
        p_tx_buff = nrf_drv_usbd_feeder_buffer_get();

        tx_size = MIN(ep_size, p_data->total_left);

        while (0 != tx_size)
        {
            size_t to_copy = MIN(tx_size, p_data->desc_left);

            /* First transfer */
            if (p_data->p_desc != NULL)
            {
                memcpy(p_tx_buff + size, p_data->p_desc, to_copy);
                p_data->p_desc = NULL;
            }
            /* Starting with second transfer */
            else if (0 < p_data->desc_left)
            {
                UNUSED_RETURN_VALUE(p_data->p_cinst->p_class_methods->feed_descriptors(
                                    &p_data->feed_thread, p_data->p_cinst,
                                    (uint8_t *)p_tx_buff + size, to_copy));
                feeding = true;
            }
            else
            {
                ;
            }

            p_data->desc_left  -= to_copy;
            p_data->total_left -= to_copy;
            tx_size            -= to_copy;
            size               += to_copy;

            /* Switch to next class if no descriptor left and first feeding was done */
            if(p_data->desc_left == 0 && feeding)
            {
                p_data->p_cinst = app_usbd_class_next_get(p_data->p_cinst);
            }

            if (0 == p_data->total_left)
            {
                continue_req = false;
            }
            else if (0 == p_data->desc_left)
            {
                if (NULL == p_data->p_cinst)
                {
                    p_data->p_desc = NULL;
                    /* No more data - check if ZLP is required */
                    if (size > 0)
                    {
                        if (size < ep_size)
                        {
                            continue_req = false;
                        }
                    }
                    break;
                }
                else
                {
                    /* New class - count descriptor size and initialize feeding thread */
                    app_usbd_class_descriptor_ctx_t desiz;
                    APP_USBD_CLASS_DESCRIPTOR_INIT(&desiz);
                    while(p_data->p_cinst->p_class_methods->feed_descriptors(
                          &desiz, p_data->p_cinst, NULL, sizeof(uint8_t)))
                    {
                        p_data->desc_left++;
                    }
                    APP_USBD_CLASS_DESCRIPTOR_INIT(&p_data->feed_thread);
                }
            }
            else
            {
                /* Nothing to do */
            }
        }
        p_next->p_data.tx = p_tx_buff;
        p_next->size      = size;
    }
    return continue_req;
}

/**
 * @brief Standard endpoint request handle.
 *
 * @param[in] p_setup_ev Setup event.
 *
 * @return Standard error code.
 */
static ret_code_t setup_endpoint_req_std(app_usbd_setup_evt_t const * p_setup_ev)
{
    if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    nrf_drv_usbd_ep_t ep_addr = (nrf_drv_usbd_ep_t)(p_setup_ev->setup.wIndex.lb);
    app_usbd_state_t usb_state = usbd_core_state_get();

    switch (p_setup_ev->setup.bRequest)
    {
        case APP_USBD_SETUP_STDREQ_GET_STATUS:
        {
            if ((usb_state == APP_USBD_STATE_Configured) || (NRF_USBD_EP_NR_GET(ep_addr) == 0))
            {
                size_t tx_size;
                uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size);

                p_tx_buff[0] = nrf_drv_usbd_ep_stall_check(ep_addr) ? 1 : 0;
                return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t));
            }
            else
            {
                return NRF_ERROR_INVALID_STATE;
            }
        }
        case APP_USBD_SETUP_STDREQ_SET_FEATURE:
        {
            if ((!NRF_USBD_EPISO_CHECK(ep_addr)) &&
                (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT))
            {
                /* Halt feature is not supported on EP0. */
                if (NRF_USBD_EP_NR_GET(ep_addr) == 0)
                {
                    return NRF_ERROR_NOT_SUPPORTED;
                }
                else if (usb_state == APP_USBD_STATE_Configured)
                {
                    nrf_drv_usbd_ep_stall(ep_addr);
                    return NRF_SUCCESS;
                }
                else
                {
                    return NRF_ERROR_INVALID_STATE;
                }
            }
            break;
        }
        case APP_USBD_SETUP_STDREQ_CLEAR_FEATURE:
        {
            if ((!NRF_USBD_EPISO_CHECK(ep_addr)) &&
                (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT))
            {
                /* Halt feature is not supported on EP0. */
                if (NRF_USBD_EP_NR_GET(ep_addr) == 0)
                {
                    return NRF_ERROR_NOT_SUPPORTED;
                }
                else if (usb_state == APP_USBD_STATE_Configured)
                {
                    nrf_drv_usbd_ep_dtoggle_clear(ep_addr);
                    nrf_drv_usbd_ep_stall_clear(ep_addr);
                    return NRF_SUCCESS;
                }
                else
                {
                    return NRF_ERROR_INVALID_STATE;
                }
            }
            break;
        }
        default:
            break;
    }
    return NRF_ERROR_NOT_SUPPORTED;
}

/**
 * @brief Standard interface request handle.
 *
 * @param[in,out] p_class_inst Class instance that holds selected interface.
 * @param[in]     iface_idx    Index of the interface in class structure.
 * @param[in]     p_event      Event structure to be processed.
 *
 * @return Operation status.
 */
static ret_code_t setup_interface_req_std_handle(
    app_usbd_class_inst_t const * const p_class_inst,
    uint8_t iface_idx,
    app_usbd_setup_evt_t  const * p_setup_ev)
{
    if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    app_usbd_state_t usb_state = usbd_core_state_get();

    if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN)
    {
        switch (p_setup_ev->setup.bRequest)
        {
            case APP_USBD_SETUP_STDREQ_GET_STATUS:
            {
                if (!(usb_state == APP_USBD_STATE_Configured))
                {
                    return NRF_ERROR_INVALID_STATE;
                }
                size_t tx_size;
                uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size);
                p_tx_buff[0] = 0;
                return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t));
            }
            case APP_USBD_SETUP_STDREQ_GET_INTERFACE:
            {
                if (!(usb_state == APP_USBD_STATE_Configured))
                {
                    return NRF_ERROR_INVALID_STATE;
                }
                size_t tx_size;
                uint8_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size);
                p_tx_buff[0] = app_usbd_iface_selection_get(p_class_inst, iface_idx);
                return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint8_t));
            }
        }
    }
    else /* APP_USBD_SETUP_REQDIR_OUT */
    {
        switch (p_setup_ev->setup.bRequest)
        {
            case APP_USBD_SETUP_STDREQ_SET_INTERFACE:
            {
                if (!(usb_state == APP_USBD_STATE_Configured))
                {
                    return NRF_ERROR_INVALID_STATE;
                }

                if(p_setup_ev->setup.wValue.w > UINT8_MAX)
                {
                    return NRF_ERROR_INVALID_PARAM;
                }
                uint8_t alt = p_setup_ev->setup.wValue.lb;
                return app_usbd_iface_select(p_class_inst, iface_idx, alt);
            }
            default:
                break;
        }
    }
    return NRF_ERROR_NOT_SUPPORTED;
}

/**
 * @brief
 *
 * Descriptors feeder handle structure.
 */
static const nrf_drv_usbd_handler_desc_t usbd_descriptor_feeder_desc =
{
    .handler = { .feeder = usbd_descriptor_conf_feeder },
    .p_context = &m_descriptor_conf_feed_data
};

static ret_code_t setup_device_req_get_status(
    app_usbd_class_inst_t const * const p_inst,
    app_usbd_setup_evt_t  const * const p_setup_ev)
{
    size_t max_size;
    uint8_t * p_trans_buff = app_usbd_core_setup_transfer_buff_get(&max_size);
    ASSERT(sizeof(uint16_t) <= max_size);

    memset(p_trans_buff, 0, sizeof(uint16_t));
    if (m_configuration_dsc.bmAttributes &
        APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_SELF_POWERED_MASK)
    {
        SET_BIT(p_trans_buff[0], 0);
    }
    if (IS_SET(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP))
    {
        SET_BIT(p_trans_buff[0], 1);
    }
    return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_trans_buff, sizeof(uint16_t));
}

static ret_code_t setup_device_req_get_descriptor(app_usbd_class_inst_t const * const p_inst,
                                                  app_usbd_setup_evt_t const * const p_setup_ev)
{
    switch (p_setup_ev->setup.wValue.hb)
    {
        case APP_USBD_DESCRIPTOR_DEVICE:
        {
            if(p_setup_ev->setup.wLength.w == 0)
            {
                return NRF_SUCCESS;
            }

            return app_usbd_core_setup_rsp(&(p_setup_ev->setup),
                                           &m_device_dsc,
                                           sizeof(m_device_dsc));
        }
        case APP_USBD_DESCRIPTOR_CONFIGURATION:
        {
            if(p_setup_ev->setup.wLength.w == 0)
            {
                return NRF_SUCCESS;
            }

            /* The size equals the size of configuration descriptor and all classes descriptors */
            const size_t size = MIN(
                sizeof(app_usbd_descriptor_configuration_t),
                p_setup_ev->setup.wLength.w);
            size_t total_length = sizeof(app_usbd_descriptor_configuration_t);
            uint8_t iface_count = 0;

            /* Iterate over all registered classes count descriptors and total size */
            app_usbd_class_inst_t const * p_class;
            for (p_class = app_usbd_class_first_get(); p_class != NULL;
                 p_class = app_usbd_class_next_get(p_class))
            {
                ASSERT(NULL != (p_class->p_class_methods));
                ASSERT(NULL != (p_class->p_class_methods->feed_descriptors));
                size_t dsc_size = 0;
                app_usbd_class_descriptor_ctx_t siz_desc;
                APP_USBD_CLASS_DESCRIPTOR_INIT(&siz_desc);
                while(p_class->p_class_methods->feed_descriptors(&siz_desc,
                                                                 p_class,
                                                                 NULL,
                                                                 sizeof(uint8_t))
                      )
                {
                    dsc_size++;
                }
                total_length += dsc_size;
                iface_count += app_usbd_class_iface_count_get(p_class);
            }

            /* Access transmission buffer */
            size_t max_size;
            app_usbd_descriptor_configuration_t * p_trans_buff =
                app_usbd_core_setup_transfer_buff_get(&max_size);
            /* Copy the configuration descriptor and update the fields that require it */
            ASSERT(size <= max_size);
            memcpy(p_trans_buff, &m_configuration_dsc, size);

            p_trans_buff->bNumInterfaces = iface_count;
            p_trans_buff->wTotalLength   = total_length;
            if (app_usbd_class_rwu_enabled_check())
            {
                p_trans_buff->bmAttributes |=
                    APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_REMOTE_WAKEUP_MASK;
            }


            m_descriptor_conf_feed_data.p_cinst    = app_usbd_class_first_get();
            m_descriptor_conf_feed_data.p_desc     = (void *)p_trans_buff;
            m_descriptor_conf_feed_data.desc_left  = size;
            m_descriptor_conf_feed_data.total_left = p_setup_ev->setup.wLength.w;

            /* Start first transfer */
            ret_code_t ret;
            CRITICAL_REGION_ENTER();

            ret = app_usbd_ep_handled_transfer(
                NRF_DRV_USBD_EPIN0,
                &usbd_descriptor_feeder_desc);

            if (NRF_SUCCESS == ret)
            {
                ret = app_usbd_core_setup_data_handler_set(
                    NRF_DRV_USBD_EPIN0,
                    &m_setup_data_handler_empty_desc);
            }
            CRITICAL_REGION_EXIT();

            return ret;
        }
        case APP_USBD_DESCRIPTOR_STRING:
        {
            if(p_setup_ev->setup.wLength.w == 0)
            {
                return NRF_SUCCESS;
            }

            app_usbd_string_desc_idx_t id =
                    (app_usbd_string_desc_idx_t)(p_setup_ev->setup.wValue.lb);
            uint16_t langid = p_setup_ev->setup.wIndex.w;
            uint16_t const * p_string_dsc = app_usbd_string_desc_get(id, langid);
            if (p_string_dsc == NULL)
            {
                return NRF_ERROR_NOT_SUPPORTED;
            }

            return app_usbd_core_setup_rsp(
                &p_setup_ev->setup,
                p_string_dsc,
                app_usbd_string_desc_length(p_string_dsc));
        }
        default:
            break;
    }


    return NRF_ERROR_NOT_SUPPORTED;
}

static ret_code_t setup_device_req_get_configuration(
    app_usbd_class_inst_t const * const p_inst,
    app_usbd_setup_evt_t  const * const p_setup_ev)
{
    size_t max_size;
    uint8_t * p_trans_buff = app_usbd_core_setup_transfer_buff_get(&max_size);
    app_usbd_state_t usb_state = usbd_core_state_get();
    if (usb_state == APP_USBD_STATE_Configured)
    {
        p_trans_buff[0] = 1;
    }
    else if (usb_state == APP_USBD_STATE_Addressed)
    {
        p_trans_buff[0] = 0;
    }
    else
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    return app_usbd_core_setup_rsp(&p_setup_ev->setup, p_trans_buff, sizeof(p_trans_buff[0]));
}

static ret_code_t setup_device_req_set_configuration(
    app_usbd_class_inst_t const * const p_inst,
    app_usbd_setup_evt_t const * const  p_setup_ev)
{
    app_usbd_state_t usb_state = usbd_core_state_get();
    if (!((usb_state == APP_USBD_STATE_Configured) ||
          (usb_state == APP_USBD_STATE_Addressed)))
    {
        return NRF_ERROR_INVALID_STATE;
    }

    if (p_setup_ev->setup.wValue.lb == 0)
    {
        app_usbd_all_iface_deselect();
        usbd_core_state_set(APP_USBD_STATE_Addressed);
    }
    else if (p_setup_ev->setup.wValue.lb == 1)
    {
        /*Clear all bulk/interrupt endpoint status and set toggle to DATA0*/
        app_usbd_all_iface_select_0();
        usbd_core_state_set(APP_USBD_STATE_Configured);
    }
    else
    {
        /*In this driver only one configuration is supported.*/
        return NRF_ERROR_INVALID_PARAM;
    }

    return NRF_SUCCESS;
}

/**
 * @brief Internal SETUP event handler.
 * @param[in] p_inst        Instance of the class.
 * @param[in] p_setup_ev    Setup request.
 * @return Standard error code.
 * @retval NRF_SUCCESS              Request handled correctly.
 * @retval NRF_ERROR_NOT_SUPPORTED  Request is not supported.
 */
static ret_code_t setup_device_req_std_handler(app_usbd_class_inst_t const * const p_inst,
                                               app_usbd_setup_evt_t const * const  p_setup_ev)
{
    ASSERT(p_inst != NULL);
    ASSERT(p_setup_ev != NULL);

    if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
    {
        return NRF_ERROR_NOT_SUPPORTED;
    }

    if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN)
    {
        switch (p_setup_ev->setup.bRequest)
        {
            case APP_USBD_SETUP_STDREQ_GET_STATUS:
            {
                return setup_device_req_get_status(p_inst, p_setup_ev);
            }
            case APP_USBD_SETUP_STDREQ_GET_DESCRIPTOR:
            {
                return setup_device_req_get_descriptor(p_inst, p_setup_ev);
            }
            case APP_USBD_SETUP_STDREQ_GET_CONFIGURATION:
            {
                return setup_device_req_get_configuration(p_inst, p_setup_ev);
            }
            default:
                /*Not supported*/
                break;
        }
        return NRF_ERROR_NOT_SUPPORTED;
    }
    else /* APP_USBD_SETUP_REQDIR_OUT */
    {
        switch (p_setup_ev->setup.bRequest)
        {
            case APP_USBD_SETUP_STDREQ_SET_ADDRESS:
            {
                ASSERT(0); /* should never reach this point */
                break;
            }
            case APP_USBD_SETUP_STDREQ_SET_FEATURE:
            {
                if (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP)
                {
                    if (!app_usbd_class_rwu_enabled_check())
                    {
                        return NRF_ERROR_FORBIDDEN;
                    }
                    SET_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP);
                    return NRF_SUCCESS;
                }
                break;
            }
            case APP_USBD_SETUP_STDREQ_CLEAR_FEATURE:
            {
                if (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP)
                {
                    if (!app_usbd_class_rwu_enabled_check())
                    {
                        return NRF_ERROR_FORBIDDEN;
                    }
                    CLR_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP);
                    return NRF_SUCCESS;
                }
                break;
            }
            case APP_USBD_SETUP_STDREQ_SET_CONFIGURATION:
            {
                return setup_device_req_set_configuration(p_inst, p_setup_ev);
            }
            case APP_USBD_SETUP_STDREQ_SET_DESCRIPTOR:
            {
                /*Not supported yet.*/
                break;
            }
            default:
                /*Not supported*/
                break;
        }
        return NRF_ERROR_NOT_SUPPORTED;
    }
}

/**
 * @brief Process SETUP command.
 *
 * Auxiliary function for SETUP command processing.
 */
static inline ret_code_t app_usbd_core_setup_req_handler(app_usbd_class_inst_t const * const p_inst)
{
    app_usbd_setup_evt_t setup_ev;
    ret_code_t ret = NRF_ERROR_NOT_SUPPORTED; /* Final result of request processing function */

    setup_ev.type = APP_USBD_EVT_DRV_SETUP;
    nrf_drv_usbd_setup_get((nrf_drv_usbd_setup_t *)&(setup_ev.setup));

    NRF_LOG_DEBUG("SETUP: t: 0x%.2x r: 0x%.2x",
                  setup_ev.setup.bmRequestType,
                  setup_ev.setup.bRequest);
    if (usb_core_ep0_handler_check())
    {
        NRF_LOG_WARNING("Previous setup not finished!");
    }
    /* Clear EP0 handler if there is anything in progress */
    usbd_core_ep0_handler_clear();

    switch (app_usbd_setup_req_rec(setup_ev.setup.bmRequestType))
    {
        case APP_USBD_SETUP_REQREC_DEVICE:
        {
            /* Endpoint 0 has core instance (that process device requests) connected */
            ret = setup_device_req_std_handler(p_inst, &setup_ev);
            if (ret == NRF_ERROR_NOT_SUPPORTED)
            {
                ret = app_usbd_all_until_served_call((app_usbd_complex_evt_t const *)&setup_ev);
            }
            break;
        }
        case APP_USBD_SETUP_REQREC_INTERFACE:
        {
            uint8_t const iface_number = setup_ev.setup.wIndex.lb;
            uint8_t iface_idx;
            app_usbd_class_inst_t const * p_inst_found = app_usbd_iface_find(
                iface_number,
                &iface_idx);
            if (p_inst_found == NULL)
            {
                ret = NRF_ERROR_INVALID_ADDR;
            }
            else
            {
                ret = app_usbd_iface_call(
                    p_inst_found, iface_idx,
                    (app_usbd_complex_evt_t const *)&setup_ev);
                if (ret == NRF_ERROR_NOT_SUPPORTED)
                {
                    ret = setup_interface_req_std_handle(p_inst_found, iface_idx, &setup_ev);
                }
            }
            break;
        }
        case APP_USBD_SETUP_REQREC_ENDPOINT:
        {
            ret = NRF_ERROR_NOT_SUPPORTED;
            nrf_drv_usbd_ep_t ep = (nrf_drv_usbd_ep_t)setup_ev.setup.wIndex.lb;
            if ((NRF_USBD_EP_NR_GET(ep) != 0)) /* For EP0 we would call this function again! */
            {
                ret = app_usbd_ep_call(ep, (app_usbd_complex_evt_t const *)&setup_ev);
            }
            if (ret == NRF_ERROR_NOT_SUPPORTED)
            {
                ret = setup_endpoint_req_std(&setup_ev);
            }
            break;
        }
        case APP_USBD_SETUP_REQREC_OTHER:
        {
            /* Try to process via every instance */
            ret = app_usbd_all_until_served_call((app_usbd_complex_evt_t const *)&setup_ev);
            break;
        }
        default:
            break;
    }

    /* Processing result */
    if (ret == NRF_SUCCESS)
    {
        if (usb_core_ep0_handler_check())
        {
            if (NRF_DRV_USBD_EPOUT0 == nrf_drv_usbd_last_setup_dir_get())
            {
                /* Request processed successfully and requires SETUP data */
                nrf_drv_usbd_setup_data_clear();
            }
        }
        else
        {
            /* Request processed successfully */
            nrf_drv_usbd_setup_clear();
        }
    }
    else
    {
        /* Request finished with error */
        nrf_drv_usbd_setup_stall();
    }
    return ret;
}

/**
 * @brief Event handler for core module.
 *
 * The event handler that would process all events directed to device.
 *
 */
static ret_code_t app_usbd_core_event_handler(app_usbd_class_inst_t const * const p_inst,
                                              app_usbd_complex_evt_t const * const p_event)
{
    ret_code_t ret = NRF_ERROR_NOT_SUPPORTED;
    switch (p_event->type)
    {
        case APP_USBD_EVT_DRV_RESET:
        {
            usbd_core_state_set(APP_USBD_STATE_Default);
            break;
        }
        case APP_USBD_EVT_DRV_SUSPEND:
        {
            ret = NRF_SUCCESS;
            break;
        }
        case APP_USBD_EVT_DRV_RESUME:
        {
            if (nrf_atomic_flag_clear_fetch(&m_rwu_pending) != 0)
            {
                nrf_usbd_task_trigger(NRF_USBD_TASK_NODRIVEDPDM);
            }

            ASSERT(usbd_core_state_get() >= APP_USBD_STATE_Unattached);
            ret = NRF_SUCCESS;
            break;
        }
        case APP_USBD_EVT_SETUP_SETADDRESS:
        {
            app_usbd_state_t usb_state = usbd_core_state_get();
            if (usb_state == APP_USBD_STATE_Default)
            {
                usbd_core_state_set(APP_USBD_STATE_Addressed);
            }
            break;
        }
        case APP_USBD_EVT_DRV_SETUP:
        {
            ret = app_usbd_core_setup_req_handler(p_inst);
            break;
        }
        case APP_USBD_EVT_INST_APPEND:
        {
            ASSERT(usbd_core_state_get() == APP_USBD_STATE_Disabled);
            ret = NRF_SUCCESS;
            break;
        }
        case APP_USBD_EVT_INST_REMOVE:
        {
            ASSERT(usbd_core_state_get() == APP_USBD_STATE_Unattached);
            usbd_core_state_set(APP_USBD_STATE_Disabled);
            ret = NRF_SUCCESS;
            break;
        }
        case APP_USBD_EVT_STARTED:
        {
            if (usbd_core_power_is_detected())
            {
                usbd_core_state_set(APP_USBD_STATE_Powered);
            }
            else
            {
                usbd_core_state_set(APP_USBD_STATE_Unattached);
            }
            ret = NRF_SUCCESS;
            break;
        }
        case APP_USBD_EVT_STOPPED:
        {
            ASSERT(usbd_core_state_get() >= APP_USBD_STATE_Powered);
            usbd_core_state_set(APP_USBD_STATE_Unattached);
            ret = NRF_SUCCESS;
            break;
        }
        /* Data transfer on endpoint 0 */
        case APP_USBD_EVT_DRV_EPTRANSFER:
        {
            if (p_event->drv_evt.data.eptransfer.status == NRF_USBD_EP_ABORTED)
            {
                /* Just ignore aborting */
                break;
            }
            /* This EPTRANSFER event has to be called only for EP0 */
            ASSERT((p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPOUT0) ||
                   (p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPIN0));
            ret = usbd_core_ep0_handler_call_and_clear(p_event->drv_evt.data.eptransfer.status);
            /* Processing result */
            if (ret == NRF_SUCCESS)
            {
                if (usb_core_ep0_handler_check())
                {
                    if (p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPOUT0)
                    {
                        /* Request processed successfully and requires SETUP data */
                        nrf_drv_usbd_setup_data_clear();
                    }
                }
                else
                {
                    nrf_drv_usbd_setup_clear();
                }
            }
            else
            {
                /* Request finished with error */
                nrf_drv_usbd_setup_stall();
            }
            break;
        }
        default:
            break;
    }

    return ret;
}

/** @} */

void app_usbd_core_ep0_enable(void)
{
    app_usbd_ep_enable(NRF_DRV_USBD_EPOUT0);
    app_usbd_ep_enable(NRF_DRV_USBD_EPIN0);
}

void app_usbd_core_ep0_disable(void)
{
    app_usbd_ep_disable(NRF_DRV_USBD_EPOUT0);
    app_usbd_ep_disable(NRF_DRV_USBD_EPIN0);
}


ret_code_t app_usbd_core_setup_rsp(app_usbd_setup_t const * p_setup,
                                   void const *             p_data,
                                   size_t                   size)
{
    size_t req_size = p_setup->wLength.w;
    size_t tx_size = MIN(req_size, size);
    bool zlp_required = (size < req_size) &&
                        (0 == (size % nrf_drv_usbd_ep_max_packet_size_get(NRF_DRV_USBD_EPIN0)));

    NRF_DRV_USBD_TRANSFER_IN_FLAGS(
        transfer,
        p_data,
        tx_size,
        zlp_required ? NRF_DRV_USBD_TRANSFER_ZLP_FLAG : 0);

    ret_code_t ret;
    CRITICAL_REGION_ENTER();
    ret = app_usbd_ep_transfer(NRF_DRV_USBD_EPIN0, &transfer);
    if (NRF_SUCCESS == ret)
    {
        ret = app_usbd_core_setup_data_handler_set(NRF_DRV_USBD_EPIN0,
                                                   &m_setup_data_handler_empty_desc);
    }
    CRITICAL_REGION_EXIT();

    return ret;
}

ret_code_t app_usbd_core_setup_data_handler_set(
        nrf_drv_usbd_ep_t ep,
        app_usbd_core_setup_data_handler_desc_t const * const p_handler_desc)
{
    if (nrf_drv_usbd_last_setup_dir_get() != ep)
    {
        return NRF_ERROR_INVALID_ADDR;
    }

    m_ep0_handler_desc = *p_handler_desc;
    return NRF_SUCCESS;
}

void * app_usbd_core_setup_transfer_buff_get(size_t * p_size)
{
    if (p_size != NULL)
        *p_size = sizeof(m_setup_transfer_buff);

    return m_setup_transfer_buff;
}

app_usbd_state_t app_usbd_core_state_get(void)
{
    return usbd_core_state_get();
}


bool app_usbd_core_feature_state_get(app_usbd_setup_stdfeature_t feature)
{
    return IS_SET(m_device_features_state, feature) ? true : false;
}

#endif //NRF_MODULE_ENABLED(APP_USBD)
