mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
457 lines
18 KiB
C++
457 lines
18 KiB
C++
/* This file tests the conversion of errors from libusbp's underlying APIs to
|
|
* libusbp_error objects. It also documents and justifies the explicit mappings
|
|
* that are in the error handling code. */
|
|
|
|
#include <test_helper.h>
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// Here is a list of the messages we want to use consistently across platforms
|
|
// to describe certain types of errors:
|
|
#define STR_TIMEOUT "The operation timed out."
|
|
#define STR_CANCELLED "The operation was cancelled."
|
|
#define STR_GENERAL_FAILURE "The request was invalid or there was an I/O problem."
|
|
#define STR_REMOVED "The device was removed."
|
|
#define STR_OVERFLOW "The transfer overflowed."
|
|
|
|
#ifdef _WIN32
|
|
|
|
TEST_CASE("error_create_winapi", "[error_create_winapi]")
|
|
{
|
|
libusbp::error error;
|
|
|
|
SECTION("includes the error code from GetLastError and its message")
|
|
{
|
|
SetLastError(0x1F4);
|
|
error.pointer_reset(error_create_winapi("Something failed."));
|
|
REQUIRE(error.message() == "Something failed. "
|
|
"User profile cannot be loaded. "
|
|
"Windows error code 0x1f4.");
|
|
}
|
|
|
|
SECTION("still works if Windows does not have a message")
|
|
{
|
|
SetLastError(0x1F431892);
|
|
error.pointer_reset(error_create_winapi("Something failed."));
|
|
REQUIRE(error.message() == "Something failed. "
|
|
"Windows error code 0x1f431892.");
|
|
}
|
|
|
|
SECTION("access denied errors")
|
|
{
|
|
// ERROR_ACCESS_DENIED can happen when running CreateFile to open a
|
|
// handle to a WinUSB device node that is already being used by another
|
|
// application. This is one of the few cases where a libusbp error
|
|
// message actually contains a troubleshooting step for the user to try.
|
|
// It is included in the library because it is a universally useful piece
|
|
// of advice for any WinUSB device.
|
|
//
|
|
// This feature is part of error_create_winapi, which means we are
|
|
// assuming that we will only ERROR_ACCESS_DENIED as a result of trying
|
|
// to open a device that is being used. If we expand the library to do
|
|
// other things that might have their access denied, this feature should
|
|
// maybe move to a different function.
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
error.pointer_reset(error_create_winapi("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
|
|
CHECK(error.message() ==
|
|
"Hi. "
|
|
"Access is denied. "
|
|
"Try closing all other programs that are using the device. "
|
|
"Windows error code 0x5.");
|
|
}
|
|
|
|
SECTION("out of memory errors")
|
|
{
|
|
// We haven't specifically seen these errors happen before, but it seems
|
|
// like either of them might be used to indicate that the system is out
|
|
// memory.
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
error.pointer_reset(error_create_winapi("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
|
|
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
error.pointer_reset(error_create_winapi("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
|
|
}
|
|
|
|
SECTION("general failure")
|
|
{
|
|
// We have seen ERROR_GEN_FAILURE occur in multiple different
|
|
// situations.
|
|
//
|
|
// It could mean that the device was disconnected from the computer
|
|
// in the middle of or during a synchronous USB transfer.
|
|
//
|
|
// It could mean that the host just sent a control transfer
|
|
// request that the device does not support, and the device has returned
|
|
// a STALL packet, which is a perfectly valid way for the device to
|
|
// behave.
|
|
//
|
|
// It probably also applies to STALL packets during IN and OUT
|
|
// transfers.
|
|
//
|
|
// The default message is "A device attached to the system is not
|
|
// functioning." This places too much blame on the USB device, so we
|
|
// definitely want libusbp to use STR_GENERAL_FAILURE instead.
|
|
SetLastError(ERROR_GEN_FAILURE);
|
|
error.pointer_reset(error_create_winapi("Hey."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_STALL));
|
|
CHECK(error.message() == "Hey. "
|
|
STR_GENERAL_FAILURE
|
|
" Windows error code 0x1f.");
|
|
}
|
|
|
|
SECTION("timeout error")
|
|
{
|
|
// The have seen WinUSB return ERROR_SEM_TIMEOUT for both synchronous
|
|
// and asynchronous operations that time out.
|
|
SetLastError(ERROR_SEM_TIMEOUT);
|
|
error.pointer_reset(error_create_winapi("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
|
|
CHECK(error.message() == "Hi. " STR_TIMEOUT " Windows error code 0x79.");
|
|
}
|
|
|
|
SECTION("cancelled error")
|
|
{
|
|
// GetOverlappedResult returns this if the asynchronous operation was
|
|
// cancelled.
|
|
SetLastError(ERROR_OPERATION_ABORTED);
|
|
error.pointer_reset(error_create_winapi("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
|
|
CHECK(error.message() == "Hi. " STR_CANCELLED " Windows error code 0x3e3.");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("error_create_overlapped")
|
|
{
|
|
libusbp::error error;
|
|
|
|
SECTION("usually just calls error_create_winapi")
|
|
{
|
|
SetLastError(ERROR_SEM_TIMEOUT);
|
|
error.pointer_reset(error_create_overlapped("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
|
|
}
|
|
|
|
SECTION("device disconnect error")
|
|
{
|
|
// WinUsb_GetOverlappedResult (and presumably also GetOverlappedResult)
|
|
// returns this error code when you are checking the status of an
|
|
// asynchronous operation for a device that has been disconnected from
|
|
// the system. This is inconsistent with the synchronous operations,
|
|
// which return ERROR_GEN_FAILURE, but it makes sense because
|
|
// GetOverlappedResult is a more general thing that applies to any type
|
|
// of file that can have asynchronous operations performed on it.
|
|
//
|
|
// It might be OK to put this feature into error_create_winapi, but in
|
|
// general, ERROR_FILE_NOT_FOUND does not mean a device was
|
|
// disconnected, so we have put this behavior in a special function
|
|
// named error_create_overlapped (instead of error_create_winapi).
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
error.pointer_reset(error_create_overlapped("Hey."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.message() == "Hey. "
|
|
"The device was disconnected. "
|
|
"Windows error code 0x2.");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("error_create_cr", "[error_create_cr]")
|
|
{
|
|
SECTION("returns the right message")
|
|
{
|
|
libusbp::error error(error_create_cr(CR_NO_SUCH_DEVNODE, "Hi."));
|
|
REQUIRE(error.message() == "Hi. CONFIGRET error code 0xd.");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
TEST_CASE("error_create_errno", "[error_create_errno]")
|
|
{
|
|
libusbp::error error;
|
|
|
|
SECTION("returns the right message")
|
|
{
|
|
errno = ENOENT;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.message() ==
|
|
"Hi. No such file or directory. Error code 2.");
|
|
}
|
|
|
|
SECTION("still works when no message is available")
|
|
{
|
|
errno = -122344;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.message() == "Hi. Error code -122344.");
|
|
}
|
|
|
|
SECTION("access denied error")
|
|
{
|
|
// EACCES is the error we see when calling open() on a USB device file
|
|
// that we don't have read-write permissions for.
|
|
errno = EACCES;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
|
|
|
|
// One reason we might not have permissions is that the udev rules might
|
|
// not have been applied yet, so usbfd_open adds the
|
|
// LIBUSBP_ERROR_NOT_READY code to EACCES errors. (That is NOT tested
|
|
// here though.)
|
|
CHECK_FALSE(error.has_code(LIBUSBP_ERROR_NOT_READY));
|
|
}
|
|
|
|
SECTION("EPERM error")
|
|
{
|
|
// EPERM is described in errno-base.h as "operation not permitted". The
|
|
// file error-codes.txt says "submission failed because urb->reject was
|
|
// set". libusb does not do anything special with this error, and I am
|
|
// not sure when we would receive it, so for now let's NOT map it to the
|
|
// LIBUSBP_ERROR_ACCESS_DENIED code.
|
|
errno = EPERM;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK_FALSE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
|
|
}
|
|
|
|
SECTION("memory errors")
|
|
{
|
|
// This is described by error-codes.txt as "no memory for allocation of
|
|
// internal structures", and it will probably be used by other non-USB
|
|
// system calls as well.
|
|
errno = ENOMEM;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
|
|
}
|
|
|
|
SECTION("general failure")
|
|
{
|
|
// error-codes.txt says that EPIPE (broken pipe) can either mean
|
|
// "endpoint stalled", a device disconnect, or the pipe type specified
|
|
// in the URB doesn't match the endpoint's actual type.
|
|
//
|
|
// It's interesting that both Windows and Linux return just a single
|
|
// error code which could either mean a STALL packet or a device
|
|
// disconnect. Maybe it's hard to tell the difference between those two
|
|
// cases because of the design of the host controller interface.
|
|
errno = EPIPE;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_STALL));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.message() == "Hi. " STR_GENERAL_FAILURE " Error code 32.");
|
|
}
|
|
|
|
SECTION("device disconnects")
|
|
{
|
|
// error-codes.txt documents ENODEV (no such device) to mean "specified
|
|
// USB-device or bus doesn't exist" or "device was removed". In
|
|
// devio.c, we can see that most ioctls will return ENODEV if the device
|
|
// is not connected. Also read() will return ENODEV if the device is
|
|
// not connected, which might be a handy way to check if the device is
|
|
// connected.
|
|
errno = ENODEV;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.message() == "Hi. " STR_REMOVED " Error code 19.");
|
|
|
|
// error-codes.txt says ESHUTDOWN (cannot send after transport endpoint
|
|
// shutdown) means "The device or host controller has been disabled due
|
|
// to some problem that could not be worked around, such as a physical
|
|
// disconnect." libusb has four places where it handles ESHUTDOWN, and
|
|
// all four of them say that the device was removed. ESHUTDOWN is the
|
|
// error we typically see from a synchronous operation when the device
|
|
// is removed in the middle of the operation.
|
|
errno = ESHUTDOWN;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.message() == "Hi. " STR_REMOVED " Error code 108.");
|
|
|
|
// error-codes.txt says EPROTO (protocol error) and ETIME (timer
|
|
// expired) (and EPIPE and EILSEQ) are codes that different kinds of
|
|
// host controller use to indicate a transfer has failed because of
|
|
// device disconnect.
|
|
|
|
errno = EPROTO;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
|
|
errno = ETIME;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
}
|
|
|
|
SECTION("timeout errors")
|
|
{
|
|
// error-codes.txt says that ETIMEDOUT indicates a timeout in a
|
|
// synchronous USB message.
|
|
errno = ETIMEDOUT;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
|
|
CHECK(error.message() == "Hi. " STR_TIMEOUT " Error code 110.");
|
|
}
|
|
|
|
SECTION("overflow errors")
|
|
{
|
|
// error-codes.txt says that EOVERFLOW (Value too large for defined data
|
|
// type) means the amount of data reutnred by the endpoint was greater
|
|
// than either the max packet size or the remaining buffer size. We
|
|
// don't have a libusbp error code for that, but we want to fix the
|
|
// misleading message from Linux.
|
|
errno = EOVERFLOW;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.message() == "Hi. " STR_OVERFLOW " Error code 75.");
|
|
}
|
|
|
|
SECTION("EILSEQ")
|
|
{
|
|
// error-codes.txt says that EILSEQ is one of "several codes that
|
|
// different kinds of host controller use to indicate a transfer has
|
|
// failed because of device disconnect".
|
|
//
|
|
// We have also seen EILSEQ happen sometimes in VirtualBox when
|
|
// cancelling an URB, or when removing a device.
|
|
//
|
|
// The default message for EILSEQ is "Invalid or incomplete multibyte or
|
|
// wide character." which is atrocious because it makes me think there
|
|
// is some bug in the software, instead of this being a USB hardware
|
|
// thing.
|
|
errno = EILSEQ;
|
|
error.pointer_reset(error_create_errno("Hi."));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
|
|
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
|
|
CHECK(error.message() == "Hi. Illegal byte sequence: "
|
|
"the device may have been disconnected or "
|
|
"the request may have been cancelled. "
|
|
"Error code 84.");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("error_from_urb_status")
|
|
{
|
|
struct usbdevfs_urb urb;
|
|
memset(&urb, 0, sizeof(urb));
|
|
libusbp::error error;
|
|
|
|
SECTION("gives a good message for cancellation (ENOENT)")
|
|
{
|
|
// error-codes.txt says the ENOENT in an URB status means that the URB
|
|
// was synchronously unlinked with usb_unlink_urb. This is the error we
|
|
// typically see for a cancelled asynchronous request (which actually
|
|
// uses usb_kill_urb, which is similar but probably slower).
|
|
urb.status = -ENOENT;
|
|
error.pointer_reset(error_from_urb_status(&urb));
|
|
REQUIRE(error.message() == STR_CANCELLED " Error code 2.");
|
|
}
|
|
|
|
SECTION("does report an error for EREMOTEIO")
|
|
{
|
|
// error-codes.txt says that EREMOTEIO means that the data read from the
|
|
// endpoint did not fill the specified buffer, and URB_SHORT_NOT_OK was
|
|
// set in urb->transfer_flags. We don't indend to set that flag, and if
|
|
// we did set that flag, then we should probably interpret a short
|
|
// transfer as an error.
|
|
//
|
|
// libusb does NOT treat EREMOTEIO as an error but that is probably
|
|
// because libusb could actually submit multiple URBs per transfer and
|
|
// some of them might not be used.
|
|
urb.status = -EREMOTEIO;
|
|
error.pointer_reset(error_from_urb_status(&urb));
|
|
REQUIRE(error.message() == "Remote I/O error. Error code 121.");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("error_create_udev", "[error_create_errno]")
|
|
{
|
|
SECTION("returns the right message")
|
|
{
|
|
libusbp::error error(error_create_udev(123, "Hi."));
|
|
REQUIRE(error.message() == "Hi. Error from libudev: 123.");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
|
|
TEST_CASE("error_create_mach")
|
|
{
|
|
SECTION("returns the right message")
|
|
{
|
|
libusbp::error error(error_create_mach(1, "Hi."));
|
|
REQUIRE(error.message() == "Hi. (os/kern) invalid address. Error code 0x1.");
|
|
}
|
|
|
|
SECTION("stall error")
|
|
{
|
|
// We encounter this error if a control transfer ends with a STALL
|
|
// packet. This is tested for in control_sync_test.cpp.
|
|
kern_return_t kr = kIOUSBPipeStalled;
|
|
libusbp::error error(error_create_mach(kr, "Hey."));
|
|
REQUIRE(error.message() == "Hey. " STR_GENERAL_FAILURE " Error code 0xe000404f.");
|
|
REQUIRE(error.has_code(LIBUSBP_ERROR_STALL));
|
|
}
|
|
|
|
SECTION("timeout")
|
|
{
|
|
// We encounter this error if a control transfer times out.
|
|
// This is tested for in control_sync_test.cpp.
|
|
kern_return_t kr = kIOUSBTransactionTimeout;
|
|
libusbp::error error(error_create_mach(kr, "Hey."));
|
|
REQUIRE(error.message() == "Hey. " STR_TIMEOUT " Error code 0xe0004051.");
|
|
REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
|
|
}
|
|
|
|
SECTION("access denied")
|
|
{
|
|
// We encounter this error if an IOUSBInterface is already open for
|
|
// exclusive access when we try to open it for exclusive access.
|
|
// This is tested for in generic_handle_test.cpp.
|
|
kern_return_t kr = kIOReturnExclusiveAccess;
|
|
libusbp::error error(error_create_mach(kr, "Hey."));
|
|
REQUIRE(error.message() == "Hey. Access is denied. "
|
|
"Try closing all other programs that are using the device. "
|
|
"Error code 0xe00002c5.");
|
|
REQUIRE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
|
|
}
|
|
|
|
SECTION("overflow")
|
|
{
|
|
// This is an error we see when we read from a pipe using an
|
|
// IOUSBInterface and the device returns more data than can fit in our
|
|
// buffer.
|
|
kern_return_t kr = kIOReturnOverrun;
|
|
libusbp::error error(error_create_mach(kr, "Hi."));
|
|
REQUIRE(error.message() == "Hi. " STR_OVERFLOW " Error code 0xe00002e8.");
|
|
}
|
|
|
|
SECTION("cancellation")
|
|
{
|
|
// This is an error we see when we call ReadPipeAsync and
|
|
// then cancel it with AbortPipe().
|
|
kern_return_t kr = kIOReturnAborted;
|
|
libusbp::error error(error_create_mach(kr, "Hi."));
|
|
REQUIRE(error.message() == "Hi. " STR_CANCELLED " Error code 0xe00002eb.");
|
|
REQUIRE(error.has_code(LIBUSBP_ERROR_CANCELLED));
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
|
|
TEST_CASE("error_create_hr", "[error_create_hr]")
|
|
{
|
|
SECTION("returns the right message")
|
|
{
|
|
libusbp::error error(error_create_hr(0x80070057, "Hi."));
|
|
REQUIRE(error.message() == "Hi. HRESULT error code 0x80070057.");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif // !NDEBUG
|