// Copyright (C) Pololu Corporation.  See www.pololu.com for details.

/*! \file libusbp.h
 *
 * This header file provides the C API for libusbp.
 */

#pragma once

/*! The major component of libusbp's version number.  In accordance with
 * semantic versioning, this gets incremented every time there is a breaking
 * change. */
#define LIBUSBP_VERSION_MAJOR 1

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#ifdef _WIN32
#include <wtypesbase.h>
#endif

#ifdef _WIN32
#define LIBUSBP_DLL_EXPORT __declspec(dllexport)
#define LIBUSBP_DLL_IMPORT __declspec(dllimport)
#else
#define LIBUSBP_DLL_IMPORT __attribute__((visibility("default")))
#define LIBUSBP_DLL_EXPORT __attribute__((visibility("default")))
#endif

#ifdef _MSC_VER
#define LIBUSBP_WARN_UNUSED _Check_return_
#else
#define LIBUSBP_WARN_UNUSED __attribute__((warn_unused_result))
#endif

#ifdef LIBUSBP_STATIC
#define LIBUSBP_API
#else
#ifdef LIBUSBP_EXPORTS
#define LIBUSBP_API LIBUSBP_DLL_EXPORT
#else
#define LIBUSBP_API LIBUSBP_DLL_IMPORT
#endif
#endif

    /*! Some functions in this library return strings to the caller via a char
     * ** argument.  If the function call was successful, it is the caller's
     * responsibility to free those strings by passing them to this function.
     * Passing the NULL pointer to this function is OK.  Do not pass any strings
     * to this function unless they were previously returned by a call to this
     * library.  Do not free the same non-NULL string twice. */
    LIBUSBP_API
    void libusbp_string_free(char*);

    /** libusbp_error
     * **************************************************************/

    /*! A libusbp_error object represents an error that occurred in the library.
     * Many functions return a libusbp_error pointer as a return value.  The
     * convention is that a NULL pointer indicates success.  If the pointer is
     * not NULL, the caller needs to free it at some point by calling
     * libusbp_error_free().
     *
     * NULL is a valid value for a libusbp_error pointer, and can be passed to
     * any function in this library that takes a libusbp_error pointer. */
    typedef struct libusbp_error libusbp_error;

    /*! Each ::libusbp_error can have 0 or more error codes that give additional
     * information about the error that might help the caller take the right
     * action when the error occurs.  This enum defines which error codes are
     * possible. */
    enum libusbp_error_code
    {
        /*! There were problems allocating memory.  A memory shortage might be
         * the root cause of the error, or there might be another error that is
         * masked by the memory problems. */
        LIBUSBP_ERROR_MEMORY = 1,

        /*! It is possible that the error was caused by a temporary condition,
         * such as the operating system taking some time to initialize drivers.
         * Any function that could return this error will say so explicitly in
         * its documentation so you do not have to worry about handling it in
         * too many places. */
        LIBUSBP_ERROR_NOT_READY = 2,

        /*! Access was denied.  A common cause of this error on Windows is that
         *  another application has a handle open to the same device. */
        LIBUSBP_ERROR_ACCESS_DENIED = 3,

        /*! The device does not have a serial number. */
        LIBUSBP_ERROR_NO_SERIAL_NUMBER = 4,

        /*! The device took too long to respond to a request or transfer data.
         */
        LIBUSBP_ERROR_TIMEOUT = 5,

        /*! The error might have been caused by the device being disconnected,
         * but it is possible it was caused by something else. */
        LIBUSBP_ERROR_DEVICE_DISCONNECTED = 6,

        /*! The error might have been caused by the host receiving a STALL
         * packet from the device, but it is possible it was caused by something
         * else. */
        LIBUSBP_ERROR_STALL = 7,

        /*! The error might have been caused by the transfer getting cancelled
         * from the host side.  Some data might have been transferred anyway. */
        LIBUSBP_ERROR_CANCELLED = 8,
    };

    /*! Attempts to copy an error.  If you copy a NULL ::libusbp_error
     * pointer, the result will also be NULL.  If you copy a non-NULL
     * ::libusbp_error pointer, the result will be non-NULL, but if there are
     * issues allocating memory, then the copied error might have different
     * properties than the original error, and it will have the
     * ::LIBUSBP_ERROR_MEMORY code.
     *
     * It is the caller's responsibility to free the copied error. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_error_copy(
        const libusbp_error*);

    /*! Frees a returned error object.  Passing the NULL pointer to this
     * function is OK.  Do not free the same non-NULL error twice. */
    LIBUSBP_API
    void libusbp_error_free(libusbp_error*);

    /*! Returns true if the error has specified error code.  The error codes are
     *  listed in the ::libusbp_error_code enum. */
    LIBUSBP_API bool libusbp_error_has_code(
        const libusbp_error*, uint32_t code);

    /*! Returns an English-language ASCII-encoded string describing the error.
     * The message consists of one or more sentences.  If there are multiple
     * sentences, the earlier ones will typically explain the context that the
     * error happened in.
     *
     * The returned pointer will be valid until the error is freed, at which
     * point it might become invalid.  Do not pass the returned pointer to
     * libusbp_string_free(). */
    LIBUSBP_API const char* libusbp_error_get_message(const libusbp_error*);

    /** libusbp_async_in_pipe
     * ******************************************************/

    /*! A libusbp_async_in_pipe is an object that holds the memory and other
     * data structures for a set of asynchronous USB requests to read data from
     * a non-zero endpoint.  It can be used to read data from a bulk or IN
     * endpoint with high throughput. */
    typedef struct libusbp_async_in_pipe libusbp_async_in_pipe;

    /*! Closes the pipe immediately.  Note that if the pipe has any
     * pending transfers, then it is possible that they cannot be freed
     * by this function.  Freeing a pipe with pending transfers could
     * cause a memory leak, but is otherwise safe. */
    LIBUSBP_API
    void libusbp_async_in_pipe_close(libusbp_async_in_pipe*);

    /*! Allocates buffers and other data structures for performing multiple
     * concurrent transfers on the pipe.
     *
     * The @a transfer_count parameter specifies how many transfers to allocate.
     * You can also think of this as the maximum number of concurrent transfers
     * that can be active at the same time.
     *
     * The @a transfer_size parameter specifies how large each transfer's buffer
     * should be, and is also the number of bytes that will be requested from
     * the operating system when the transfer is submitted.
     *
     * It is best to set the transfer size to a multiple of the maximum packet
     * size of the endpoint.  Otherwise, you might get an error when the device
     * sends more data than can fit in the transfer's buffer.  This type of
     * error is called an overflow.
     *
     * If you want to be reading the pipe every millisecond without gaps, you
     * should set the transfer count high enough so that it would take about 100
     * ms to 250 ms to finish all the transfers.  As long as the operating
     * system runs your process that often, you should be able to keep the USB
     * host controller busy permanently.  (Though we have observed gaps in the
     * transfers when trying to do this inside a VirtualBox machine.)
     *
     * You should not set the transfer count too high, or else it might end up
     * taking a long time to cancel the transfers when you are closing the pipe.
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_allocate_transfers(
        libusbp_async_in_pipe*, size_t transfer_count, size_t transfer_size);

    /*! Starts reading data from the pipe. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_start_endless_transfers(libusbp_async_in_pipe*);

    /*! Checks for new events, such as a transfer completing.  This
     * function and libusbp_async_in_pipe_handle_finished_transfer() should
     * be called regularly in order to get data from the pipe. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe*);

    /*! Retrieves a boolean saying whether there are any pending
     * transfers.  A pending transfer is a transfer that was submitted to
     * the operating system, and it may have been completed, but it has
     * not been passed to the caller yet via
     * libusbp_async_in_pipe_handle_finished_transfer(). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_has_pending_transfers(
        libusbp_async_in_pipe*, bool* result);

    /*! Checks to see if there is a finished transfer that can be handled.
     * If there is one, then this function retrieves the data from the
     * transfer, the number of bytes transferred, and any error that might
     * have occurred related to the transfer.
     *
     * @param finished An optional output pointer used to return a pointer that
     * indicates whether a transfer was finished.  If the returned value is
     * false, then no transfer was finished, and there is no transfer_error or
     * data to handle.
     *
     * @param buffer An optional output pointer used to return the data
     * from the transfer.  The buffer must be at least as large as the
     * transfer size specifed when
     * libusbp_async_in_pipe_allocate_transfers was called.
     *
     * @param transferred An optional output pointer used to return the
     * number of bytes transferred.
     *
     * @param transfer_error An optional pointer used to return an error
     * related to the transfer, such as a timeout or a cancellation.  If
     * this pointer is provided, and a non-NULL error is returned via it,
     * then the error must later be freed with libusbp_error_free().  There
     * will never be a non-NULL transfer error if there is a regular error
     * returned as the return value of this function. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_handle_finished_transfer(libusbp_async_in_pipe*,
        bool* finished,
        void* buffer,
        size_t* transferred,
        libusbp_error** transfer_error);

    /*! Cancels all the transfers for this pipe.  The cancellation is
     * asynchronous, so it won't have an immediate effect.  If you want
     * to actually make sure that all the transfers get cancelled, you
     * will need to call libusbp_async_in_pipe_handle_events() and
     * libusbp_async_in_pipe_handle_finished_transfer() repeatedly until
     * libusbp_async_in_pipe_has_pending_transfers() indicates there are
     * no pending transfers left. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe*);

    /** libusbp_device
     * *************************************************************/

    /*! Represents a single USB device.  A composite device with multiple
     * functions is represented by a single libusbp_device object.
     *
     * A NULL libusbp_device pointer is valid and can be passed to any function
     * in this library that takes such pointers.  */
    typedef struct libusbp_device libusbp_device;

    /*! Finds all the USB devices connected to the computer and returns a list
     * of them.
     *
     * The optional @a device_count parameter is used to return the number of
     * devices in the list.  The list is actually one element larger because it
     * ends with a NULL pointer.
     *
     * If this function is successful (the returned error pointer is NULL), then
     * you must later free each device by calling libusbp_device_free() and free
     * the list by calling libusbp_list_free().  The order in which the
     * retrieved objects are freed does not matter. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_list_connected_devices(
        libusbp_device*** device_list, size_t* device_count);

    /*! Frees a device list returned by libusbp_list_connected_device(). */
    LIBUSBP_API
    void libusbp_list_free(libusbp_device** list);

    /*! Finds a device with the specified vendor ID and product ID and returns a
     * pointer to it.  If no device can be found, returns a NULL pointer.  If
     * the retrieved device pointer is not NULL, you must free it later by
     * calling libusbp_device_free().  The retrieved device pointer will always
     * be NULL if an error is returned. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_find_device_with_vid_pid(
        uint16_t vendor_id, uint16_t product_id, libusbp_device** device);

    /*! Makes a copy of a device object.  If this function is successful, you
     * will need to free the copy by calling libusbp_device_free() at some
     * point. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_device_copy(
        const libusbp_device* source, libusbp_device** dest);

    /*! Frees a device object.  Passing a NULL pointer to this function is OK.
     * Do not free the same non-NULL device twice. */
    LIBUSBP_API void libusbp_device_free(libusbp_device*);

    /*! Gets the USB vendor ID of the device (idVendor). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_device_get_vendor_id(
        const libusbp_device*, uint16_t* vendor_id);

    /*! Gets the USB product ID of the device (idProduct). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_device_get_product_id(const libusbp_device*, uint16_t* product_id);

    /*! Gets the USB revision code of the device (bcdDevice). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_device_get_revision(
        const libusbp_device*, uint16_t* revision);

    /*! Gets the serial number of the device as an ASCII-encoded string.
     *
     * On Windows, this just returns the segment of the Device Instance ID after
     * the last slash.  If the device does not have a serial number, it will
     * return some other type of identifier that contains andpersands (&).
     * Windows ignores serial numbers with invalid characters in them.  For more
     * information, see:
     *
     *   https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn
     *
     * On other systems, if the device does not have a serial number, then this
     * function returns an error with the code ::LIBUSBP_ERROR_NO_SERIAL_NUMBER.
     *
     * (Most applications should only call this function on specific USB devices
     * that are already known to have serial numbers, in which case the lack of
     * a serial number really does indicate a failure.)
     *
     * You should free the returned string by calling libusbp_string_free(). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_device_get_serial_number(
        const libusbp_device*, char** serial_number);

    /*! Gets an operating system-specific string that identifies the device.
     *
     * Note that the level of specificity provided by the ID depends on the
     * system you are on, and whether your device has a USB serial number.  As
     * long as the device remains connected to the bus, this ID is not expected
     * to change and there should be no other devices that have the same ID.
     * However, if the device gets disconnected from the bus, it may be possible
     * for the ID to be reused by another device.
     *
     * @b Windows: This will be a device instance ID, and it will look something
     * like this:
     *
     * <pre>
     * USB\\VID_1FFB&PID_DA01\6&11A23516&18&0000
     * </pre>
     *
     * If your device has a serial number, the part after the slash will be the
     * serial number.  Otherwise, it will be a string with andpersands in it.
     *
     * @b Linux: This will be a sysfs path, and it will look like something like
     * this:
     *
     * <pre>
     * /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2
     * </pre>
     *
     * <b>macOS:</b> This will be an integer from
     * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number
     * with no leading zeros.  It will look something like this:
     *
     * <pre>
     * 10000021a
     * </pre>
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_device_get_os_id(
        const libusbp_device*, char** id);

    /** libusbp_generic_interface
     * **************************************************/

    /*! Represents a generic or vendor-defined interface of a USB device.  A
     * null libusbp_generic_interface pointer is valid and can be passed to any
     * function in this library that takes such pointers. */
    typedef struct libusbp_generic_interface libusbp_generic_interface;

    /*! Creates a generic interface object for a specified interface of the
     * specified USB device.  This function does as many checks as possible to
     * make sure that a handle to the interface could be opened, without
     * actually opening it yet.
     *
     * On all platforms, if a record of the interface cannot be found, then an
     * error is returned with the code LIBUSBP_ERROR_NOT_READY, because this
     * could just be a temporary condition that happens right after the device
     * is plugged in.
     *
     * On Windows, the generic interface must use the WinUSB driver, or this
     * function will fail.  If it is using no driver, that could be a temporary
     * condition, and the error returned will use the LIBUSBP_ERROR_NOT_READY
     * error code.
     *
     * On Linux, if the corresponding devnode file does not exist, an error with
     * code LIBUSBP_ERROR_NOT_READY is returned.  If the interface is assigned
     * to a driver that is not "usbfs", an error is returned.
     *
     * On macOS, we do not have any additional checks beyond just making sure
     * that an entry for the interface is found.  For non-composite devices,
     * that check is deferred until a handle is opened.
     *
     * @param interface_number The lowest @a bInterfaceNumber for the interfaces
     * in the USB function you want to use.
     *
     * @param composite Should be true if the device is composite, and false
     * otherwise.
     *
     * The returned object must be freed with libusbp_generic_interface_free().
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_generic_interface_create(const libusbp_device*,
        uint8_t interface_number,
        bool composite,
        libusbp_generic_interface**);

    /*! Frees the specified generic interface object.  Passing the NULL pointer
     * to this function is OK.  Do not free the same non-NULL pointer twice. */
    LIBUSBP_API void libusbp_generic_interface_free(libusbp_generic_interface*);

    /*! Makes a copy of the generic interface object.  The copy must be freed
     * with libusbp_generic_interface_free(). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_generic_interface_copy(const libusbp_generic_interface* source,
        libusbp_generic_interface** dest);

    /*! Returns an operating system-specific string that can be used to uniquely
     * identify this generic interface.
     *
     * <b>Windows:</b> This will be a device instance ID specific to this
     * interface, and it will look something like this:
     *
     * <pre>
     * USB\\VID_1FFB&PID_DA01&MI_00\6&11A23516&18&0000
     * </pre>
     *
     * <b>Linux:</b> This will be a sysfs path specific to this interface, and
     * it will look like something like this:
     *
     * <pre>
     * /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0
     * </pre>
     *
     * <b>macOS:</b> This will be an integer from
     * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number
     * with no leading zeros.  It will look something like this:
     *
     * <pre>
     * 10000021a
     * </pre>
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_generic_interface_get_os_id(
        const libusbp_generic_interface*, char** id);

    /*! Returns an operating system-specific filename corresponding to this
     * interface.
     *
     * <b>Windows:</b> This will be the name of a file you can use with
     * CreateFile to access the device, and it will look something like this:
     *
     * <pre>
     * \\\\?\\usb#vid_1ffb&pid_da01&mi_00#6&11a23516&18&0000#{99c4bbb0-e925-4397-afee-981cd0702163}
     * </pre>
     *
     * <b>Linux:</b> this will return a device node file name that represents
     * the overall USB device.  It will look something like:
     *
     * <pre>
     * /dev/bus/usb/001/007
     * </pre>
     *
     * <b>macOS:</b> This will be an integer from
     * IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number
     * with no leading zeros.  It will look something like this:
     *
     * <pre>
     * 10000021a
     * </pre>
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_generic_interface_get_os_filename(
        const libusbp_generic_interface*, char** filename);

    /** libusbp_generic_handle
     * *****************************************************/

    /*! Represents a generic handle to a USB device.  This handle can be used to
     * perform operations such as control transfers and reading and writing data
     * from non-zero endpoints.
     *
     * NULL is a valid value for a libusbp_generic_handle pointer, and can be
     * passed in any functions of this library that take a
     * libusbp_generic_handle pointer. */
    typedef struct libusbp_generic_handle libusbp_generic_handle;

    /*! Opens a generic handle to the specified interface of a USB device which
     * can be used to perform USB I/O operations.
     *
     * The handle must later be closed with libusbp_generic_handle_close().
     *
     * On Windows, for devices using WinUSB, if another application has a handle
     * open already when this function is called, then this function will fail
     * and the returned error will have code ::LIBUSBP_ERROR_ACCESS_DENIED.
     *
     * On macOS, this function will set the device's configuration to 1 as a
     * side effect in case it is not already configured. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_generic_handle_open(
        const libusbp_generic_interface*, libusbp_generic_handle**);

    /*! Closes and frees the specified generic handle.  It is OK to pass NULL to
     * this function.  Do not close the same non-NULL handle twice. All
     * ::libusbp_async_in_pipe objects created by the handle must be closed
     * before closing the handle. */
    LIBUSBP_API
    void libusbp_generic_handle_close(libusbp_generic_handle*);

    /*! Creates a new asynchronous pipe object for reading data in from the
     * device on one of its bulk or interrupt IN endpoints.
     *
     * The behavior of this library is unspecified if you use both an
     * asynchronous IN pipe and synchronous reads with libusbp_read_pipe() on
     * the same pipe of the same generic handle.  One reason for that is because
     * for WinUSB devices, this function enables RAW_IO for the pipe, and it
     * does not turn off RAW_IO again after the pipe is closed.  So the behavior
     * of libusbp_read_pipe() could change depending on whether an asynchronous
     * IN pipe has been used. */
    LIBUSBP_API
    libusbp_error* libusbp_generic_handle_open_async_in_pipe(
        libusbp_generic_handle*,
        uint8_t pipe_id,
        libusbp_async_in_pipe** async_in_pipe);

    /*! Sets a timeout for a particular pipe on the USB device.
     *
     * The @a pipe_id should either be 0 to specify control transfers on
     * endpoint 0, or should be a bEndpointAddress value from one of the
     * device's endpoint descriptors.  Specifying an invalid pipe might result
     * in an error.
     *
     * The timeout value is specified in milliseconds, and a value of 0 means no
     * timeout (wait forever).  If this function is not called, the default
     * behavior of the handle is to have no timeout.
     *
     * The behavior of this function is unspecified if there is any data being
     * transferred on the pipe while this function is running.
     *
     * It is unspecified whether this timeout has an effect on asynchronous
     * transfers. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error*
    libusbp_generic_handle_set_timeout(
        libusbp_generic_handle*, uint8_t pipe_id, uint32_t timeout);

    /*! Performs a synchronous (blocking) control transfer on endpoint 0.
     *
     * Under Linux, this blocking transfer unfortunately cannot be interrupted
     * with Ctrl+C.
     *
     * The @a buffer parameter should point to a buffer that is at least @a
     * wLength bytes long.
     *
     * The @a transferred pointer is optional, and is used to return the number
     * of bytes that were actually transferred.
     *
     * The direction of the transfer is determined by the @a bmRequestType
     * parameter.
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_control_transfer(
        libusbp_generic_handle*,
        uint8_t bmRequestType,
        uint8_t bRequest,
        uint16_t wValue,
        uint16_t wIndex,
        void* buffer,
        uint16_t wLength,
        size_t* transferred);

    /*! Performs a synchronous (blocking) write of data to a bulk or interrupt
     * endpoint.
     *
     * Under Linux, this blocking transfer unfortunately cannot be interrupted
     * with Ctrl+C.
     *
     * The @a pipe_id parameter specifies which endpoint to use.  This argument
     * should be bEndpointAddress value from one of the device's IN endpoint
     * descriptors.  (Its most significant bit must be 0.)
     *
     * The @a transferred parameter is an optional pointer to a variable that
     * will receive the number of bytes transferred. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_write_pipe(
        libusbp_generic_handle*,
        uint8_t pipe_id,
        const void* buffer,
        size_t size,
        size_t* transferred);

    /*! Performs a synchronous (blocking) read of data from a bulk or interrupt
     * endpoint.
     *
     * It is best to set the buffer size to a multiple of the maximum
     * packet size of the endpoint.  Otherwise, this function might return
     * an error when the device sends more data than can fit in the
     * buffer.  This type of error is called an overflow.
     *
     * Under Linux, this blocking transfer unfortunately cannot be interrupted
     * with Ctrl+C.
     *
     * The @a pipe_id parameter specifies which endpoint to use.  This argument
     * should be bEndpointAddress value from one of the device's IN endpoint
     * descriptors.  (Its most significant bit must be 1.)
     *
     * The @a transferred parameter is an optional pointer to a variable that
     * will receive the number of bytes transferred. */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_read_pipe(
        libusbp_generic_handle*,
        uint8_t pipe_id,
        void* buffer,
        size_t size,
        size_t* transferred);

#ifdef __linux__
    /*! Gets the underlying file descriptor of the generic handle.  This
     * function is only available on Linux, and is intended for advanced users.
     * The returned file descriptor will remain open and valid as long as the
     * handle is open and has not been closed. */
    LIBUSBP_API
    int libusbp_generic_handle_get_fd(libusbp_generic_handle*);
#endif

#ifdef _WIN32
    /*! Gets the underlying WinUSB handle for the generic handle.  This function
     * is only available on Windows, and is intended for advanced users.  The
     * returned WinUSB handle will remain open and valid as long as the generic
     * handle is open and has not been closed. */
    LIBUSBP_API
    HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle*);
#endif

#ifdef __APPLE__
    /*! Gets the underlying IOCFPlugInInterface object representing the
     * interface. You can cast the returned pointer to a `IOCFPlugInInterface
     * **` and then use `QueryInterface` to get the corresponding
     * `IOUSBInterfaceInterface **`. There is an example of this in
     * generic_handle_test.cpp. */
    LIBUSBP_API
    void** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle*);
#endif

    /** libusbp_serial_port
     * ********************************************************/

    /*! Represents a serial port. A null libusbp_serial_port pointer is valid
     * and can be passed to any function in this library that takes such
     * pointers. */
    typedef struct libusbp_serial_port libusbp_serial_port;

    /*! Creates a serial port object for a specified interface of the
     * specified USB device.
     *
     * On all platforms, if a record of the interface cannot be found, then an
     * error is returned with the code LIBUSBP_ERROR_NOT_READY, because this
     * could just be a temporary condition that happens right after the device
     * is plugged in.
     *
     * On macOS, it is assumed that the interface with a @a bInterfaceNumber one
     * greater than @a interface_number is the interface that the
     * IOSerialBSDClient will attach to.  This should be true if the device
     * implements the USB CDC ACM class and has ordered its interfaces so that
     * the control interface is right before the data interface.
     *
     * @param interface_number The lowest @a bInterfaceNumber for the USB
     * interfaces that comprise the serial port.
     *
     * @param composite Should be true if the device is composite, and false
     * otherwise.
     *
     * The returned object must be freed with libusbp_generic_interface_free().
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_serial_port_create(
        const libusbp_device*,
        uint8_t interface_number,
        bool composite,
        libusbp_serial_port**);

    /*! Frees the specified serial port object.  Passing the NULL pointer to
     * this function is OK.  Do not free the same non-NULL pointer twice. */
    LIBUSBP_API void libusbp_serial_port_free(libusbp_serial_port*);

    /*! Makes a copy of the generic interface object.  The copy must be freed
     * with libusbp_generic_interface_free(). */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_serial_port_copy(
        const libusbp_serial_port* source, libusbp_serial_port** dest);

    /*! Gets the user-friendly name of the COM port
     * that could be used to open a handle.
     *
     * On Windows, this will be something like "COM12".
     *
     * On Linux, it will be something like "/dev/ttyACM0".
     *
     * On macOS, it will be something like "/dev/cu.usbmodem012345".
     * Specifically, it will be a call-out device, not a dial-in device.
     *
     * You should free the returned string by calling libusbp_string_free().
     */
    LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error* libusbp_serial_port_get_name(
        const libusbp_serial_port*, char** name);

#ifdef __cplusplus
}
#endif
