summaryrefslogtreecommitdiff
path: root/sanei/sanei_usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'sanei/sanei_usb.c')
-rw-r--r--sanei/sanei_usb.c1822
1 files changed, 1810 insertions, 12 deletions
diff --git a/sanei/sanei_usb.c b/sanei/sanei_usb.c
index 6fd6040..db0f452 100644
--- a/sanei/sanei_usb.c
+++ b/sanei/sanei_usb.c
@@ -49,6 +49,7 @@
#include "../include/sane/config.h"
#include <stdlib.h>
+#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -62,6 +63,9 @@
#include <dirent.h>
#include <time.h>
+#if WITH_USB_RECORD_REPLAY
+#include <libxml/tree.h>
+#endif
#ifdef HAVE_RESMGR
#include <resmgr.h>
@@ -174,6 +178,34 @@ static int device_number=0;
* count number of time sanei_usb has been initialized */
static int initialized=0;
+typedef enum
+{
+ sanei_usb_testing_mode_disabled = 0,
+
+ sanei_usb_testing_mode_record, // records the communication with the slave
+ // but does not change the USB stack in any
+ // way
+ sanei_usb_testing_mode_replay, // replays the communication with the scanner
+ // recorded earlier
+}
+sanei_usb_testing_mode;
+
+// Whether testing mode has been enabled
+static sanei_usb_testing_mode testing_mode = sanei_usb_testing_mode_disabled;
+
+#if WITH_USB_RECORD_REPLAY
+static int testing_development_mode = 0;
+int testing_known_commands_input_failed = 0;
+unsigned testing_last_known_seq = 0;
+SANE_String testing_record_backend = NULL;
+xmlNode* testing_append_commands_node = NULL;
+
+// XML file from which we read testing data
+SANE_String testing_xml_path = NULL;
+xmlDoc* testing_xml_doc = NULL;
+xmlNode* testing_xml_next_tx_node = NULL;
+#endif // WITH_USB_RECORD_REPLAY
+
#if defined(HAVE_LIBUSB_LEGACY) || defined(HAVE_LIBUSB)
static int libusb_timeout = 30 * 1000; /* 30 seconds */
#endif /* HAVE_LIBUSB_LEGACY */
@@ -455,6 +487,859 @@ sanei_libusb_strerror (int errcode)
}
#endif /* HAVE_LIBUSB */
+#if WITH_USB_RECORD_REPLAY
+SANE_Status sanei_usb_testing_enable_replay(SANE_String_Const path,
+ int development_mode)
+{
+ testing_mode = sanei_usb_testing_mode_replay;
+ testing_development_mode = development_mode;
+
+ // TODO: we'll leak if noone ever inits sane_usb properly
+ testing_xml_path = strdup(path);
+ testing_xml_doc = xmlReadFile(testing_xml_path, NULL, 0);
+ if (!testing_xml_doc)
+ return SANE_STATUS_ACCESS_DENIED;
+
+ return SANE_STATUS_GOOD;
+}
+
+#define FAIL_TEST(func, ...) \
+ do { \
+ DBG(1, "%s: FAIL: ", func); \
+ DBG(1, __VA_ARGS__); \
+ fail_test(); \
+ } while (0)
+
+#define FAIL_TEST_TX(func, node, ...) \
+ do { \
+ sanei_xml_print_seq_if_any(node, func); \
+ DBG(1, "%s: FAIL: ", func); \
+ DBG(1, __VA_ARGS__); \
+ fail_test(); \
+ } while (0)
+
+void fail_test()
+{
+}
+
+SANE_Status sanei_usb_testing_enable_record(SANE_String_Const path, SANE_String_Const be_name)
+{
+ testing_mode = sanei_usb_testing_mode_record;
+ testing_record_backend = strdup(be_name);
+ testing_xml_path = strdup(path);
+
+ return SANE_STATUS_GOOD;
+}
+
+static xmlNode* sanei_xml_find_first_child_with_name(xmlNode* parent,
+ const char* name)
+{
+ xmlNode* curr_child = xmlFirstElementChild(parent);
+ while (curr_child != NULL)
+ {
+ if (xmlStrcmp(curr_child->name, (const xmlChar*)name) == 0)
+ return curr_child;
+ curr_child = xmlNextElementSibling(curr_child);
+ }
+ return NULL;
+}
+
+static xmlNode* sanei_xml_find_next_child_with_name(xmlNode* child,
+ const char* name)
+{
+ xmlNode* curr_child = xmlNextElementSibling(child);
+ while (curr_child != NULL)
+ {
+ if (xmlStrcmp(curr_child->name, (const xmlChar*)name) == 0)
+ return curr_child;
+ curr_child = xmlNextElementSibling(curr_child);
+ }
+ return NULL;
+}
+
+// a wrapper to get rid of -Wpointer-sign warnings in a single place
+static char* sanei_xml_get_prop(xmlNode* node, const char* name)
+{
+ return (char*)xmlGetProp(node, (const xmlChar*)name);
+}
+
+// returns -1 if attribute is not found
+static int sanei_xml_get_prop_uint(xmlNode* node, const char* name)
+{
+ char* attr = sanei_xml_get_prop(node, name);
+ if (attr == NULL)
+ {
+ return -1;
+ }
+
+ unsigned attr_uint = strtoul(attr, NULL, 0);
+ xmlFree(attr);
+ return attr_uint;
+}
+
+static void sanei_xml_print_seq_if_any(xmlNode* node, const char* parent_fun)
+{
+ char* attr = sanei_xml_get_prop(node, "seq");
+ if (attr == NULL)
+ return;
+
+ DBG(1, "%s: FAIL: in transaction with seq %s:\n", parent_fun, attr);
+ xmlFree(attr);
+}
+
+// Checks whether transaction should be ignored. We ignore get_descriptor and
+// set_configuration transactions. The latter is ignored because
+// set_configuration is called in sanei_usb_open outside test path.
+static int sanei_xml_is_transaction_ignored(xmlNode* node)
+{
+ if (xmlStrcmp(node->name, (const xmlChar*)"control_tx") != 0)
+ return 0;
+
+ if (sanei_xml_get_prop_uint(node, "endpoint_number") != 0)
+ return 0;
+
+ int is_direction_in = 0;
+ int is_direction_out = 0;
+
+ char* attr = sanei_xml_get_prop(node, "direction");
+ if (attr == NULL)
+ return 0;
+
+ if (strcmp(attr, "IN") == 0)
+ is_direction_in = 1;
+ if (strcmp(attr, "OUT") == 0)
+ is_direction_out = 1;
+ xmlFree(attr);
+
+ unsigned bRequest = sanei_xml_get_prop_uint(node, "bRequest");
+ if (bRequest == USB_REQ_GET_DESCRIPTOR && is_direction_in)
+ {
+ if (sanei_xml_get_prop_uint(node, "bmRequestType") != 0x80)
+ return 0;
+ return 1;
+ }
+ if (bRequest == USB_REQ_SET_CONFIGURATION && is_direction_out)
+ return 1;
+
+ return 0;
+}
+
+static xmlNode* sanei_xml_skip_non_tx_nodes(xmlNode* node)
+{
+ const char* known_node_names[] = {
+ "control_tx", "bulk_tx", "interrupt_tx", "debug", "known_commands_end"
+ };
+
+ while (node != NULL)
+ {
+ int found = 0;
+ for (unsigned i = 0; i < sizeof(known_node_names) /
+ sizeof(known_node_names[0]); ++i)
+ {
+ if (xmlStrcmp(node->name, (const xmlChar*) known_node_names[i]) == 0)
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found && sanei_xml_is_transaction_ignored(node) == 0)
+ {
+ break;
+ }
+
+ node = xmlNextElementSibling(node);
+ }
+ return node;
+}
+
+static int sanei_xml_is_known_commands_end(xmlNode* node)
+{
+ if (!testing_development_mode || node == NULL)
+ return 0;
+ return xmlStrcmp(node->name, (const xmlChar*)"known_commands_end") == 0;
+}
+
+// returns next transaction node that is not get_descriptor
+static xmlNode* sanei_xml_peek_next_tx_node()
+{
+ return testing_xml_next_tx_node;
+}
+
+// returns next transaction node that is not get_descriptor
+static xmlNode* sanei_xml_get_next_tx_node()
+{
+ xmlNode* next = testing_xml_next_tx_node;
+
+ if (sanei_xml_is_known_commands_end(next))
+ {
+ testing_append_commands_node = xmlPreviousElementSibling(next);
+ return next;
+ }
+
+ testing_xml_next_tx_node =
+ xmlNextElementSibling(testing_xml_next_tx_node);
+
+ testing_xml_next_tx_node =
+ sanei_xml_skip_non_tx_nodes(testing_xml_next_tx_node);
+
+ return next;
+}
+
+#define CHAR_TYPE_INVALID -1
+#define CHAR_TYPE_SPACE -2
+
+static int8_t sanei_xml_char_types[256] =
+{
+ /* 0x00-0x0f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1,
+ /* 0x10-0x1f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x20-0x2f */ -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x30-0x3f */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ /* 0x40-0x4f */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x50-0x5f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x60-0x6f */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x70-0x7f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x80-0x8f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0x90-0x9f */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xa0-0xaf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xb0-0xbf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xc0-0xcf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xd0-0xdf */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xe0-0xef */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 0xf0-0xff */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+static char* sanei_xml_get_hex_data_slow_path(xmlNode* node, xmlChar* content, xmlChar* cur_content,
+ char* ret_data, char* cur_ret_data, size_t* size)
+{
+ int num_nibbles = 0;
+ unsigned cur_nibble = 0;
+
+ while (*cur_content != 0)
+ {
+ while (sanei_xml_char_types[(uint8_t)*cur_content] == CHAR_TYPE_SPACE)
+ cur_content++;
+
+ if (*cur_content == 0)
+ break;
+
+ // don't use stroul because it will parse in big-endian and data is in
+ // little endian
+ uint8_t c = *cur_content;
+ int8_t ci = sanei_xml_char_types[c];
+ if (ci == CHAR_TYPE_INVALID)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected character %c\n", c);
+ cur_content++;
+ continue;
+ }
+
+ cur_nibble = (cur_nibble << 4) | ci;
+ num_nibbles++;
+
+ if (num_nibbles == 2)
+ {
+ *cur_ret_data++ = cur_nibble;
+ cur_nibble = 0;
+ num_nibbles = 0;
+ }
+ cur_content++;
+ }
+ *size = cur_ret_data - ret_data;
+ xmlFree(content);
+ return ret_data;
+}
+
+// Parses hex data in XML text node in the format of '00 11 ab 3f', etc. to
+// binary string. The size is returned as *size. The caller is responsible for
+// freeing the returned value
+static char* sanei_xml_get_hex_data(xmlNode* node, size_t* size)
+{
+ xmlChar* content = xmlNodeGetContent(node);
+
+ // let's overallocate to simplify the implementation. We expect the string
+ // to be deallocated soon anyway
+ char* ret_data = malloc(strlen((const char*)content) / 2 + 2);
+ char* cur_ret_data = ret_data;
+
+ xmlChar* cur_content = content;
+
+ // the text to binary conversion takes most of the time spent in tests, so we
+ // take extra care to optimize it. We split the implementation into fast and
+ // slow path. The fast path utilizes the knowledge that there will be no spaces
+ // within bytes. When this assumption does not hold, we switch to the slow path.
+ while (*cur_content != 0)
+ {
+ // most of the time there will be 1 or 2 spaces between bytes. Give the CPU
+ // chance to predict this by partially unrolling the while loop.
+ if (sanei_xml_char_types[(uint8_t)*cur_content] == CHAR_TYPE_SPACE)
+ {
+ cur_content++;
+ if (sanei_xml_char_types[(uint8_t)*cur_content] == CHAR_TYPE_SPACE)
+ {
+ cur_content++;
+ while (sanei_xml_char_types[(uint8_t)*cur_content] == CHAR_TYPE_SPACE)
+ cur_content++;
+ }
+ }
+
+ if (*cur_content == 0)
+ break;
+
+ // don't use stroul because it will parse in big-endian and data is in
+ // little endian
+ int8_t ci1 = sanei_xml_char_types[(uint8_t)*cur_content];
+ int8_t ci2 = sanei_xml_char_types[(uint8_t)*(cur_content + 1)];
+
+ if (ci1 < 0 || ci2 < 0)
+ return sanei_xml_get_hex_data_slow_path(node, content, cur_content, ret_data, cur_ret_data,
+ size);
+
+ *cur_ret_data++ = ci1 << 4 | ci2;
+ cur_content += 2;
+ }
+ *size = cur_ret_data - ret_data;
+ xmlFree(content);
+ return ret_data;
+}
+
+// caller is responsible for freeing the returned pointer
+static char* sanei_binary_to_hex_data(const char* data, size_t size,
+ size_t* out_size)
+{
+ char* hex_data = malloc(size * 4);
+ size_t hex_size = 0;
+
+ for (size_t i = 0; i < size; ++i)
+ {
+ hex_size += snprintf(hex_data + hex_size, 3, "%02hhx", data[i]);
+ if (i + 1 != size)
+ {
+ if ((i + 1) % 32 == 0)
+ hex_data[hex_size++] = '\n';
+ else
+ hex_data[hex_size++] = ' ';
+ }
+ }
+ hex_data[hex_size] = 0;
+ if (out_size)
+ *out_size = hex_size;
+ return hex_data;
+}
+
+
+static void sanei_xml_set_data(xmlNode* node, const char* data)
+{
+ // FIXME: remove existing children
+ xmlAddChild(node, xmlNewText((const xmlChar*)data));
+}
+
+// Writes binary data to XML node as a child text node in the hex format of
+// '00 11 ab 3f'.
+static void sanei_xml_set_hex_data(xmlNode* node, const char* data,
+ size_t size)
+{
+ char* hex_data = sanei_binary_to_hex_data(data, size, NULL);
+ sanei_xml_set_data(node, hex_data);
+ free(hex_data);
+}
+
+static void sanei_xml_set_hex_attr(xmlNode* node, const char* attr_name,
+ unsigned attr_value)
+{
+ const int buf_size = 128;
+ char buf[buf_size];
+ if (attr_value > 0xffffff)
+ snprintf(buf, buf_size, "0x%x", attr_value);
+ else if (attr_value > 0xffff)
+ snprintf(buf, buf_size, "0x%06x", attr_value);
+ else if (attr_value > 0xff)
+ snprintf(buf, buf_size, "0x%04x", attr_value);
+ else
+ snprintf(buf, buf_size, "0x%02x", attr_value);
+
+ xmlNewProp(node, (const xmlChar*)attr_name, (const xmlChar*)buf);
+}
+
+static void sanei_xml_set_uint_attr(xmlNode* node, const char* attr_name,
+ unsigned attr_value)
+{
+ const int buf_size = 128;
+ char buf[buf_size];
+ snprintf(buf, buf_size, "%d", attr_value);
+ xmlNewProp(node, (const xmlChar*)attr_name, (const xmlChar*)buf);
+}
+
+static xmlNode* sanei_xml_append_command(xmlNode* sibling,
+ int indent, xmlNode* e_command)
+{
+ if (indent)
+ {
+ xmlNode* e_indent = xmlNewText((const xmlChar*)"\n ");
+ sibling = xmlAddNextSibling(sibling, e_indent);
+ }
+ return xmlAddNextSibling(sibling, e_command);
+}
+
+static void sanei_xml_command_common_props(xmlNode* node, int endpoint_number,
+ const char* direction)
+{
+ xmlNewProp(node, (const xmlChar*)"time_usec", (const xmlChar*)"0");
+ sanei_xml_set_uint_attr(node, "seq", ++testing_last_known_seq);
+ sanei_xml_set_uint_attr(node, "endpoint_number", endpoint_number);
+ xmlNewProp(node, (const xmlChar*)"direction", (const xmlChar*)direction);
+}
+
+static void sanei_xml_record_seq(xmlNode* node)
+{
+ int seq = sanei_xml_get_prop_uint(node, "seq");
+ if (seq > 0)
+ testing_last_known_seq = seq;
+}
+
+static void sanei_xml_break()
+{
+}
+
+static void sanei_xml_break_if_needed(xmlNode* node)
+{
+ char* attr = sanei_xml_get_prop(node, "debug_break");
+ if (attr != NULL)
+ {
+ sanei_xml_break();
+ xmlFree(attr);
+ }
+}
+
+// returns 1 on success
+static int sanei_usb_check_attr(xmlNode* node, const char* attr_name,
+ const char* expected, const char* parent_fun)
+{
+ char* attr = sanei_xml_get_prop(node, attr_name);
+ if (attr == NULL)
+ {
+ FAIL_TEST_TX(parent_fun, node, "no %s attribute\n", attr_name);
+ return 0;
+ }
+
+ if (strcmp(attr, expected) != 0)
+ {
+ FAIL_TEST_TX(parent_fun, node, "unexpected %s attribute: %s, wanted %s\n",
+ attr_name, attr, expected);
+ xmlFree(attr);
+ return 0;
+ }
+ xmlFree(attr);
+ return 1;
+}
+
+// returns 1 on success
+static int sanei_usb_attr_is(xmlNode* node, const char* attr_name,
+ const char* expected)
+{
+ char* attr = sanei_xml_get_prop(node, attr_name);
+ if (attr == NULL)
+ return 0;
+
+ if (strcmp(attr, expected) != 0)
+ {
+ xmlFree(attr);
+ return 0;
+ }
+ xmlFree(attr);
+ return 1;
+}
+
+// returns 0 on success
+static int sanei_usb_check_attr_uint(xmlNode* node, const char* attr_name,
+ unsigned expected, const char* parent_fun)
+{
+ char* attr = sanei_xml_get_prop(node, attr_name);
+ if (attr == NULL)
+ {
+ FAIL_TEST_TX(parent_fun, node, "no %s attribute\n", attr_name);
+ return 0;
+ }
+
+ unsigned attr_int = strtoul(attr, NULL, 0);
+ if (attr_int != expected)
+ {
+ FAIL_TEST_TX(parent_fun, node,
+ "unexpected %s attribute: %s, wanted 0x%x\n",
+ attr_name, attr, expected);
+ xmlFree(attr);
+ return 0;
+ }
+ xmlFree(attr);
+ return 1;
+}
+
+static int sanei_usb_attr_is_uint(xmlNode* node, const char* attr_name,
+ unsigned expected)
+{
+ char* attr = sanei_xml_get_prop(node, attr_name);
+ if (attr == NULL)
+ return 0;
+
+ unsigned attr_int = strtoul(attr, NULL, 0);
+ if (attr_int != expected)
+ {
+ xmlFree(attr);
+ return 0;
+ }
+ xmlFree(attr);
+ return 1;
+}
+
+// returns 1 on data equality
+static int sanei_usb_check_data_equal(xmlNode* node,
+ const char* data,
+ size_t data_size,
+ const char* expected_data,
+ size_t expected_size,
+ const char* parent_fun)
+{
+ if ((data_size == expected_size) &&
+ (memcmp(data, expected_data, data_size) == 0))
+ return 1;
+
+ char* data_hex = sanei_binary_to_hex_data(data, data_size, NULL);
+ char* expected_hex = sanei_binary_to_hex_data(expected_data, expected_size,
+ NULL);
+
+ if (data_size == expected_size)
+ FAIL_TEST_TX(parent_fun, node, "data differs (size %lu):\n", data_size);
+ else
+ FAIL_TEST_TX(parent_fun, node,
+ "data differs (got size %lu, expected %lu):\n",
+ data_size, expected_size);
+
+ FAIL_TEST(parent_fun, "got: %s\n", data_hex);
+ FAIL_TEST(parent_fun, "expected: %s\n", expected_hex);
+ free(data_hex);
+ free(expected_hex);
+ return 0;
+}
+
+SANE_String sanei_usb_testing_get_backend()
+{
+ if (testing_xml_doc == NULL)
+ return NULL;
+
+ xmlNode* el_root = xmlDocGetRootElement(testing_xml_doc);
+ if (xmlStrcmp(el_root->name, (const xmlChar*)"device_capture") != 0)
+ {
+ FAIL_TEST(__func__, "the given file is not USB capture\n");
+ return NULL;
+ }
+
+ char* attr = sanei_xml_get_prop(el_root, "backend");
+ if (attr == NULL)
+ {
+ FAIL_TEST(__func__, "no backend attr in description node\n");
+ return NULL;
+ }
+ // duplicate using strdup so that the caller can use free()
+ char* ret = strdup(attr);
+ xmlFree(attr);
+ return ret;
+}
+
+SANE_Bool sanei_usb_is_replay_mode_enabled()
+{
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ return SANE_TRUE;
+
+ return SANE_FALSE;
+}
+
+static void sanei_usb_record_debug_msg(xmlNode* node, SANE_String_Const message)
+{
+ int node_was_null = node == NULL;
+ if (node_was_null)
+ node = testing_append_commands_node;
+
+ xmlNode* e_tx = xmlNewNode(NULL, (const xmlChar*)"debug");
+ sanei_xml_set_uint_attr(e_tx, "seq", ++testing_last_known_seq);
+ xmlNewProp(e_tx, (const xmlChar*)"message", (const xmlChar*)message);
+
+ node = sanei_xml_append_command(node, node_was_null, e_tx);
+
+ if (node_was_null)
+ testing_append_commands_node = node;
+}
+
+static void sanei_usb_record_replace_debug_msg(xmlNode* node, SANE_String_Const message)
+{
+ if (!testing_development_mode)
+ return;
+
+ testing_last_known_seq--;
+ sanei_usb_record_debug_msg(node, message);
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+}
+
+static void sanei_usb_replay_debug_msg(SANE_String_Const message)
+{
+ if (testing_known_commands_input_failed)
+ return;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return;
+ }
+
+ if (sanei_xml_is_known_commands_end(node))
+ {
+ sanei_usb_record_debug_msg(NULL, message);
+ return;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"debug") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ sanei_usb_record_replace_debug_msg(node, message);
+ }
+
+ if (!sanei_usb_check_attr(node, "message", message, __func__))
+ {
+ sanei_usb_record_replace_debug_msg(node, message);
+ }
+}
+
+extern void sanei_usb_testing_record_message(SANE_String_Const message)
+{
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+ sanei_usb_record_debug_msg(NULL, message);
+ }
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ sanei_usb_replay_debug_msg(message);
+ }
+}
+
+static void sanei_usb_add_endpoint(device_list_type* device,
+ SANE_Int transfer_type,
+ SANE_Int ep_address,
+ SANE_Int ep_direction);
+
+static SANE_Status sanei_usb_testing_init()
+{
+ DBG_INIT();
+
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+ testing_xml_doc = xmlNewDoc((const xmlChar*)"1.0");
+ return SANE_STATUS_GOOD;
+ }
+
+ if (device_number != 0)
+ return SANE_STATUS_INVAL; // already opened
+
+ xmlNode* el_root = xmlDocGetRootElement(testing_xml_doc);
+ if (xmlStrcmp(el_root->name, (const xmlChar*)"device_capture") != 0)
+ {
+ DBG(1, "%s: the given file is not USB capture\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ xmlNode* el_description =
+ sanei_xml_find_first_child_with_name(el_root, "description");
+ if (el_description == NULL)
+ {
+ DBG(1, "%s: could not find description node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ int device_id = sanei_xml_get_prop_uint(el_description, "id_vendor");
+ if (device_id < 0)
+ {
+ DBG(1, "%s: no id_vendor attr in description node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ int product_id = sanei_xml_get_prop_uint(el_description, "id_product");
+ if (product_id < 0)
+ {
+ DBG(1, "%s: no id_product attr in description node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ xmlNode* el_configurations =
+ sanei_xml_find_first_child_with_name(el_description, "configurations");
+ if (el_configurations == NULL)
+ {
+ DBG(1, "%s: could not find configurations node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ xmlNode* el_configuration =
+ sanei_xml_find_first_child_with_name(el_configurations, "configuration");
+ if (el_configuration == NULL)
+ {
+ DBG(1, "%s: no configuration nodes\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ while (el_configuration != NULL)
+ {
+ xmlNode* el_interface =
+ sanei_xml_find_first_child_with_name(el_configuration, "interface");
+
+ while (el_interface != NULL)
+ {
+ device_list_type device;
+ memset(&device, 0, sizeof(device));
+ device.devname = strdup(testing_xml_path);
+
+ // other code shouldn't depend on methon because testing_mode is
+ // sanei_usb_testing_mode_replay
+ device.method = sanei_usb_method_libusb;
+ device.vendor = device_id;
+ device.product = product_id;
+
+ device.interface_nr = sanei_xml_get_prop_uint(el_interface, "number");
+ if (device.interface_nr < 0)
+ {
+ DBG(1, "%s: no number attr in interface node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ xmlNode* el_endpoint =
+ sanei_xml_find_first_child_with_name(el_interface, "endpoint");
+
+ while (el_endpoint != NULL)
+ {
+ char* transfer_attr = sanei_xml_get_prop(el_endpoint,
+ "transfer_type");
+ int address = sanei_xml_get_prop_uint(el_endpoint, "address");
+ char* direction_attr = sanei_xml_get_prop(el_endpoint,
+ "direction");
+
+ int direction_is_in = strcmp(direction_attr, "IN") == 0 ? 1 : 0;
+ int transfer_type = -1;
+ if (strcmp(transfer_attr, "INTERRUPT") == 0)
+ transfer_type = USB_ENDPOINT_TYPE_INTERRUPT;
+ else if (strcmp(transfer_attr, "BULK") == 0)
+ transfer_type = USB_ENDPOINT_TYPE_BULK;
+ else if (strcmp(transfer_attr, "ISOCHRONOUS") == 0)
+ transfer_type = USB_ENDPOINT_TYPE_ISOCHRONOUS;
+ else if (strcmp(transfer_attr, "CONTROL") == 0)
+ transfer_type = USB_ENDPOINT_TYPE_CONTROL;
+ else
+ {
+ DBG(3, "%s: unknown endpoint type %s\n",
+ __func__, transfer_attr);
+ }
+
+ if (transfer_type >= 0)
+ {
+ sanei_usb_add_endpoint(&device, transfer_type, address,
+ direction_is_in);
+ }
+
+ xmlFree(transfer_attr);
+ xmlFree(direction_attr);
+
+ el_endpoint =
+ sanei_xml_find_next_child_with_name(el_endpoint, "endpoint");
+ }
+ device.alt_setting = 0;
+ device.missing = 0;
+
+ memcpy(&(devices[device_number]), &device, sizeof(device));
+ device_number++;
+
+ el_interface = sanei_xml_find_next_child_with_name(el_interface,
+ "interface");
+ }
+ el_configuration =
+ sanei_xml_find_next_child_with_name(el_configurations,
+ "configuration");
+ }
+
+ xmlNode* el_transactions =
+ sanei_xml_find_first_child_with_name(el_root, "transactions");
+
+ if (el_transactions == NULL)
+ {
+ DBG(1, "%s: could not find transactions node\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ xmlNode* el_transaction = xmlFirstElementChild(el_transactions);
+ el_transaction = sanei_xml_skip_non_tx_nodes(el_transaction);
+
+ if (el_transaction == NULL)
+ {
+ DBG(1, "%s: no transactions within capture\n", __func__);
+ return SANE_STATUS_INVAL;
+ }
+
+ testing_xml_next_tx_node = el_transaction;
+
+ return SANE_STATUS_GOOD;
+}
+
+static void sanei_usb_testing_exit()
+{
+ if (testing_development_mode || testing_mode == sanei_usb_testing_mode_record)
+ {
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+ xmlAddNextSibling(testing_append_commands_node, xmlNewText((const xmlChar*)"\n "));
+ free(testing_record_backend);
+ }
+ xmlSaveFileEnc(testing_xml_path, testing_xml_doc, "UTF-8");
+ }
+ xmlFreeDoc(testing_xml_doc);
+ free(testing_xml_path);
+ xmlCleanupParser();
+}
+#else // WITH_USB_RECORD_REPLAY
+SANE_Status sanei_usb_testing_enable_replay(SANE_String_Const path,
+ int development_mode)
+{
+ (void) path;
+ (void) development_mode;
+
+ DBG(1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+SANE_Status sanei_usb_testing_enable_record(SANE_String_Const path, SANE_String_Const be_name)
+{
+ (void) path;
+ (void) be_name;
+
+ DBG(1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+SANE_String sanei_usb_testing_get_backend()
+{
+ return NULL;
+}
+
+SANE_Bool sanei_usb_is_replay_mode_enabled()
+{
+ return SANE_FALSE;
+}
+
+void sanei_usb_testing_record_message(SANE_String_Const message)
+{
+ (void) message;
+}
+#endif // WITH_USB_RECORD_REPLAY
+
void
sanei_usb_init (void)
{
@@ -473,6 +1358,26 @@ sanei_usb_init (void)
if(device_number==0)
memset (devices, 0, sizeof (devices));
+#if WITH_USB_RECORD_REPLAY
+ if (testing_mode != sanei_usb_testing_mode_disabled)
+ {
+ if (initialized == 0)
+ {
+ if (sanei_usb_testing_init() != SANE_STATUS_GOOD)
+ {
+ DBG(1, "%s: failed initializing fake USB stack\n", __func__);
+ return;
+ }
+ }
+
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ initialized++;
+ return;
+ }
+ }
+#endif
+
/* initialize USB with old libusb library */
#ifdef HAVE_LIBUSB_LEGACY
DBG (4, "%s: Looking for libusb devices\n", __func__);
@@ -499,7 +1404,12 @@ sanei_usb_init (void)
}
#ifdef DBG_LEVEL
if (DBG_LEVEL > 4)
+#if LIBUSB_API_VERSION >= 0x01000106
+ libusb_set_option (sanei_usb_ctx, LIBUSB_OPTION_LOG_LEVEL,
+ LIBUSB_LOG_LEVEL_INFO);
+#else
libusb_set_debug (sanei_usb_ctx, 3);
+#endif /* LIBUSB_API_VERSION */
#endif /* DBG_LEVEL */
}
#endif /* HAVE_LIBUSB */
@@ -533,6 +1443,13 @@ int i;
/* if we reach 0, free allocated resources */
if(initialized==0)
{
+#if WITH_USB_RECORD_REPLAY
+ if (testing_mode != sanei_usb_testing_mode_disabled)
+ {
+ sanei_usb_testing_exit();
+ }
+#endif // WITH_USB_RECORD_REPLAY
+
/* free allocated resources */
DBG (4, "%s: freeing resources\n", __func__);
for (i = 0; i < device_number; i++)
@@ -1038,6 +1955,11 @@ sanei_usb_scan_devices (void)
return;
}
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ // device added in sanei_usb_testing_init()
+ return;
+ }
/* we mark all already detected devices as missing */
/* each scan method will reset this value to 0 (not missing)
* when storing the device */
@@ -1262,7 +2184,7 @@ sanei_usb_set_endpoint (SANE_Int dn, SANE_Int ep_type, SANE_Int ep)
}
}
-#if HAVE_LIBUSB_LEGACY || HAVE_LIBUSB || HAVE_USBCALLS
+#if HAVE_LIBUSB_LEGACY || HAVE_LIBUSB || HAVE_USBCALLS || WITH_USB_RECORD_REPLAY
static const char* sanei_usb_transfer_type_desc(SANE_Int transfer_type)
{
switch (transfer_type)
@@ -1365,6 +2287,64 @@ sanei_usb_get_endpoint (SANE_Int dn, SANE_Int ep_type)
}
}
+#if WITH_USB_RECORD_REPLAY
+static void sanei_usb_record_open(SANE_Int dn)
+{
+ xmlNode* e_root = xmlNewNode(NULL, (const xmlChar*) "device_capture");
+ xmlDocSetRootElement(testing_xml_doc, e_root);
+ xmlNewProp(e_root, (const xmlChar*)"backend", (const xmlChar*) testing_record_backend);
+
+ xmlNode* e_description = xmlNewChild(e_root, NULL, (const xmlChar*) "description", NULL);
+ sanei_xml_set_hex_attr(e_description, "id_vendor", devices[dn].vendor);
+ sanei_xml_set_hex_attr(e_description, "id_product", devices[dn].product);
+
+ xmlNode* e_configurations = xmlNewChild(e_description, NULL,
+ (const xmlChar*) "configurations", NULL);
+ xmlNode* e_configuration = xmlNewChild(e_configurations, NULL,
+ (const xmlChar*) "configuration", NULL);
+ sanei_xml_set_uint_attr(e_configuration, "number", 1);
+
+ xmlNode* e_interface = xmlNewChild(e_configuration, NULL, (const xmlChar*) "interface", NULL);
+ sanei_xml_set_uint_attr(e_interface, "number", devices[dn].interface_nr);
+
+ struct endpoint_data_desc {
+ const char* transfer_type;
+ const char* direction;
+ SANE_Int ep_address;
+ };
+
+ struct endpoint_data_desc endpoints[8] =
+ {
+ { "BULK", "IN", devices[dn].bulk_in_ep },
+ { "BULK", "OUT", devices[dn].bulk_out_ep },
+ { "ISOCHRONOUS", "IN", devices[dn].iso_in_ep },
+ { "ISOCHRONOUS", "OUT", devices[dn].iso_out_ep },
+ { "INTERRUPT", "IN", devices[dn].int_in_ep },
+ { "INTERRUPT", "OUT", devices[dn].int_out_ep },
+ { "CONTROL", "IN", devices[dn].control_in_ep },
+ { "CONTROL", "OUT", devices[dn].control_out_ep }
+ };
+
+ for (int i = 0; i < 8; ++i)
+ {
+ if (endpoints[i].ep_address)
+ {
+ xmlNode* e_endpoint = xmlNewChild(e_interface, NULL, (const xmlChar*)"endpoint", NULL);
+ xmlNewProp(e_endpoint, (const xmlChar*)"transfer_type",
+ (const xmlChar*) endpoints[i].transfer_type);
+ sanei_xml_set_uint_attr(e_endpoint, "number", endpoints[i].ep_address & 0x0f);
+ xmlNewProp(e_endpoint, (const xmlChar*)"direction",
+ (const xmlChar*) endpoints[i].direction);
+ sanei_xml_set_hex_attr(e_endpoint, "address", endpoints[i].ep_address);
+ }
+ }
+ xmlNode* e_transactions = xmlNewChild(e_root, NULL, (const xmlChar*)"transactions", NULL);
+
+ // add an empty node so that we have something to append to
+ testing_append_commands_node = xmlAddChild(e_transactions, xmlNewText((const xmlChar*)""));;
+}
+#endif // WITH_USB_RECORD_REPLAY
+
SANE_Status
sanei_usb_open (SANE_String_Const devname, SANE_Int * dn)
{
@@ -1400,7 +2380,13 @@ sanei_usb_open (SANE_String_Const devname, SANE_Int * dn)
return SANE_STATUS_INVAL;
}
- if (devices[devcount].method == sanei_usb_method_libusb)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ DBG (1, "sanei_usb_open: opening fake USB device\n");
+ // the device configuration has been already filled in
+ // sanei_usb_testing_init()
+ }
+ else if (devices[devcount].method == sanei_usb_method_libusb)
{
#ifdef HAVE_LIBUSB_LEGACY
struct usb_device *dev;
@@ -1915,6 +2901,16 @@ sanei_usb_open (SANE_String_Const devname, SANE_Int * dn)
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ sanei_usb_record_open(devcount);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
devices[devcount].open = SANE_TRUE;
*dn = devcount;
DBG (3, "sanei_usb_open: opened usb device `%s' (*dn=%d)\n",
@@ -1948,7 +2944,11 @@ sanei_usb_close (SANE_Int dn)
dn);
return;
}
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ DBG (1, "sanei_usb_close: closing fake USB device\n");
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
close (devices[dn].fd);
else if (devices[dn].method == sanei_usb_method_usbcalls)
{
@@ -2001,6 +3001,9 @@ sanei_usb_close (SANE_Int dn)
void
sanei_usb_set_timeout (SANE_Int __sane_unused__ timeout)
{
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ return;
+
#if defined(HAVE_LIBUSB_LEGACY) || defined(HAVE_LIBUSB)
libusb_timeout = timeout;
#else
@@ -2028,6 +3031,9 @@ sanei_usb_clear_halt (SANE_Int dn)
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ return SANE_STATUS_GOOD;
+
#ifdef HAVE_LIBUSB_LEGACY
int ret;
@@ -2085,6 +3091,9 @@ sanei_usb_clear_halt (SANE_Int dn)
SANE_Status
sanei_usb_reset (SANE_Int __sane_unused__ dn)
{
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ return SANE_STATUS_GOOD;
+
#ifdef HAVE_LIBUSB_LEGACY
int ret;
@@ -2110,6 +3119,160 @@ sanei_usb_reset (SANE_Int __sane_unused__ dn)
return SANE_STATUS_GOOD;
}
+#if WITH_USB_RECORD_REPLAY
+// returns non-negative value on success, -1 on failure
+static int sanei_usb_replay_next_read_bulk_packet_size(SANE_Int dn)
+{
+ xmlNode* node = sanei_xml_peek_next_tx_node();
+ if (node == NULL)
+ return -1;
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"bulk_tx") != 0)
+ {
+ return -1;
+ }
+
+ if (!sanei_usb_attr_is(node, "direction", "IN"))
+ return -1;
+ if (!sanei_usb_attr_is_uint(node, "endpoint_number",
+ devices[dn].bulk_in_ep & 0x0f))
+ return -1;
+
+ size_t got_size = 0;
+ char* got_data = sanei_xml_get_hex_data(node, &got_size);
+ free(got_data);
+ return got_size;
+}
+
+static void sanei_usb_record_read_bulk(xmlNode* node, SANE_Int dn,
+ SANE_Byte* buffer,
+ size_t size, ssize_t read_size)
+{
+ int node_was_null = node == NULL;
+ if (node_was_null)
+ node = testing_append_commands_node;
+
+ xmlNode* e_tx = xmlNewNode(NULL, (const xmlChar*)"bulk_tx");
+ sanei_xml_command_common_props(e_tx, devices[dn].bulk_in_ep & 0x0f, "IN");
+
+ if (buffer == NULL)
+ {
+ const int buf_size = 128;
+ char buf[buf_size];
+ snprintf(buf, buf_size, "(unknown read of allowed size %ld)", size);
+ xmlNode* e_content = xmlNewText((const xmlChar*)buf);
+ xmlAddChild(e_tx, e_content);
+ }
+ else
+ {
+ if (read_size >= 0)
+ {
+ sanei_xml_set_hex_data(e_tx, (const char*)buffer, read_size);
+ }
+ else
+ {
+ xmlNewProp(e_tx, (const xmlChar*)"error", (const xmlChar*)"timeout");
+ }
+ }
+
+ node = sanei_xml_append_command(node, node_was_null, e_tx);
+
+ if (node_was_null)
+ testing_append_commands_node = node;
+}
+
+static void sanei_usb_record_replace_read_bulk(xmlNode* node, SANE_Int dn,
+ SANE_Byte* buffer,
+ size_t size, size_t read_size)
+{
+ if (!testing_development_mode)
+ return;
+ testing_known_commands_input_failed = 1;
+ testing_last_known_seq--;
+ sanei_usb_record_read_bulk(node, dn, buffer, size, read_size);
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+}
+
+static int sanei_usb_replay_read_bulk(SANE_Int dn, SANE_Byte* buffer,
+ size_t size)
+{
+ // libusb may potentially combine multiple IN packets into a single transfer.
+ // We recontruct that by looking into the next packet. If it can be
+ // included into the current transfer without
+ size_t wanted_size = size;
+ size_t total_got_size = 0;
+ while (wanted_size > 0)
+ {
+ if (testing_known_commands_input_failed)
+ return -1;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return -1;
+ }
+
+ if (sanei_xml_is_known_commands_end(node))
+ {
+ sanei_usb_record_read_bulk(NULL, dn, NULL, 0, size);
+ testing_known_commands_input_failed = 1;
+ return -1;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"bulk_tx") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ sanei_usb_record_replace_read_bulk(node, dn, NULL, 0, wanted_size);
+ return -1;
+ }
+
+ if (!sanei_usb_check_attr(node, "direction", "IN", __func__))
+ {
+ sanei_usb_record_replace_read_bulk(node, dn, NULL, 0, wanted_size);
+ return -1;
+ }
+ if (!sanei_usb_check_attr_uint(node, "endpoint_number",
+ devices[dn].bulk_in_ep & 0x0f,
+ __func__))
+ {
+ sanei_usb_record_replace_read_bulk(node, dn, NULL, 0, wanted_size);
+ return -1;
+ }
+
+ size_t got_size = 0;
+ char* got_data = sanei_xml_get_hex_data(node, &got_size);
+
+ if (got_size > wanted_size)
+ {
+ FAIL_TEST_TX(__func__, node,
+ "got more data than wanted (%lu vs %lu)\n",
+ got_size, wanted_size);
+ free(got_data);
+ sanei_usb_record_replace_read_bulk(node, dn, NULL, 0, wanted_size);
+ return -1;
+ }
+
+ memcpy(buffer + total_got_size, got_data, got_size);
+ free(got_data);
+ total_got_size += got_size;
+ wanted_size -= got_size;
+
+ int next_size = sanei_usb_replay_next_read_bulk_packet_size(dn);
+ if (next_size < 0)
+ return total_got_size;
+ if ((size_t) next_size > wanted_size)
+ return total_got_size;
+ }
+ return total_got_size;
+}
+#endif // WITH_USB_RECORD_REPLAY
+
SANE_Status
sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
{
@@ -2129,7 +3292,16 @@ sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
DBG (5, "sanei_usb_read_bulk: trying to read %lu bytes\n",
(unsigned long) *size);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+#if WITH_USB_RECORD_REPLAY
+ read_size = sanei_usb_replay_read_bulk(dn, buffer, *size);
+#else
+ DBG(1, "%s: USB record-replay mode support missing\n", __func__);
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
read_size = read (devices[dn].fd, buffer, *size);
@@ -2236,8 +3408,22 @@ sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ sanei_usb_record_read_bulk(NULL, dn, buffer, *size, read_size);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
if (read_size < 0)
{
+ *size = 0;
+ if (testing_mode != sanei_usb_testing_mode_disabled)
+ return SANE_STATUS_IO_ERROR;
+
#ifdef HAVE_LIBUSB_LEGACY
if (devices[dn].method == sanei_usb_method_libusb)
usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
@@ -2245,7 +3431,6 @@ sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
if (devices[dn].method == sanei_usb_method_libusb)
libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_in_ep);
#endif
- *size = 0;
return SANE_STATUS_IO_ERROR;
}
if (read_size == 0)
@@ -2263,6 +3448,165 @@ sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
return SANE_STATUS_GOOD;
}
+#if WITH_USB_RECORD_REPLAY
+static int sanei_usb_record_write_bulk(xmlNode* node, SANE_Int dn,
+ const SANE_Byte* buffer,
+ size_t size, size_t write_size)
+{
+ int node_was_null = node == NULL;
+ if (node_was_null)
+ node = testing_append_commands_node;
+
+ xmlNode* e_tx = xmlNewNode(NULL, (const xmlChar*)"bulk_tx");
+ sanei_xml_command_common_props(e_tx, devices[dn].bulk_out_ep & 0x0f, "OUT");
+ sanei_xml_set_hex_data(e_tx, (const char*)buffer, size);
+ // FIXME: output write_size
+
+ node = sanei_xml_append_command(node, node_was_null, e_tx);
+
+ if (node_was_null)
+ testing_append_commands_node = node;
+ return write_size;
+}
+
+static void sanei_usb_record_replace_write_bulk(xmlNode* node, SANE_Int dn,
+ const SANE_Byte* buffer,
+ size_t size, size_t write_size)
+{
+ if (!testing_development_mode)
+ return;
+ testing_last_known_seq--;
+ sanei_usb_record_write_bulk(node, dn, buffer, size, write_size);
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+}
+
+// returns non-negative value on success, -1 on failure
+static int sanei_usb_replay_next_write_bulk_packet_size(SANE_Int dn)
+{
+ xmlNode* node = sanei_xml_peek_next_tx_node();
+ if (node == NULL)
+ return -1;
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"bulk_tx") != 0)
+ {
+ return -1;
+ }
+
+ if (!sanei_usb_attr_is(node, "direction", "OUT"))
+ return -1;
+ if (!sanei_usb_attr_is_uint(node, "endpoint_number",
+ devices[dn].bulk_out_ep & 0x0f))
+ return -1;
+
+ size_t got_size = 0;
+ char* got_data = sanei_xml_get_hex_data(node, &got_size);
+ free(got_data);
+ return got_size;
+}
+
+static int sanei_usb_replay_write_bulk(SANE_Int dn, const SANE_Byte* buffer,
+ size_t size)
+{
+ size_t wanted_size = size;
+ size_t total_wrote_size = 0;
+ while (wanted_size > 0)
+ {
+ if (testing_known_commands_input_failed)
+ return -1;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return -1;
+ }
+
+ if (sanei_xml_is_known_commands_end(node))
+ {
+ sanei_usb_record_write_bulk(NULL, dn, buffer, size, size);
+ return size;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"bulk_tx") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size, size);
+ return -1;
+ }
+
+ if (!sanei_usb_check_attr(node, "direction", "OUT", __func__))
+ {
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size, size);
+ return -1;
+ }
+ if (!sanei_usb_check_attr_uint(node, "endpoint_number",
+ devices[dn].bulk_out_ep & 0x0f,
+ __func__))
+ {
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size, size);
+ return -1;
+ }
+
+ size_t wrote_size = 0;
+ char* wrote_data = sanei_xml_get_hex_data(node, &wrote_size);
+
+ if (wrote_size > wanted_size)
+ {
+ FAIL_TEST_TX(__func__, node,
+ "wrote more data than wanted (%lu vs %lu)\n",
+ wrote_size, wanted_size);
+ if (!testing_development_mode)
+ {
+ free(wrote_data);
+ return -1;
+ }
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size, size);
+ wrote_size = size;
+ }
+ else if (!sanei_usb_check_data_equal(node,
+ ((const char*) buffer) +
+ total_wrote_size,
+ wrote_size,
+ wrote_data, wrote_size,
+ __func__))
+ {
+ if (!testing_development_mode)
+ {
+ free(wrote_data);
+ return -1;
+ }
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size,
+ size);
+ wrote_size = size;
+ }
+
+ free(wrote_data);
+ if (wrote_size < wanted_size &&
+ sanei_usb_replay_next_write_bulk_packet_size(dn) < 0)
+ {
+ FAIL_TEST_TX(__func__, node,
+ "wrote less data than wanted (%lu vs %lu)\n",
+ wrote_size, wanted_size);
+ if (!testing_development_mode)
+ {
+ return -1;
+ }
+ sanei_usb_record_replace_write_bulk(node, dn, buffer, size,
+ size);
+ wrote_size = size;
+ }
+ total_wrote_size += wrote_size;
+ wanted_size -= wrote_size;
+ }
+ return total_wrote_size;
+}
+#endif
+
SANE_Status
sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
{
@@ -2284,7 +3628,16 @@ sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
if (debug_level > 10)
print_buffer (buffer, *size);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+#if WITH_USB_RECORD_REPLAY
+ write_size = sanei_usb_replay_write_bulk(dn, buffer, *size);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
write_size = write (devices[dn].fd, buffer, *size);
@@ -2391,9 +3744,22 @@ sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ sanei_usb_record_write_bulk(NULL, dn, buffer, *size, write_size);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
if (write_size < 0)
{
*size = 0;
+ if (testing_mode != sanei_usb_testing_mode_disabled)
+ return SANE_STATUS_IO_ERROR;
+
#ifdef HAVE_LIBUSB_LEGACY
if (devices[dn].method == sanei_usb_method_libusb)
usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
@@ -2409,6 +3775,161 @@ sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
return SANE_STATUS_GOOD;
}
+#if WITH_USB_RECORD_REPLAY
+static void
+sanei_usb_record_control_msg(xmlNode* node,
+ SANE_Int dn, SANE_Int rtype, SANE_Int req,
+ SANE_Int value, SANE_Int index, SANE_Int len,
+ const SANE_Byte* data)
+{
+ (void) dn;
+
+ int node_was_null = node == NULL;
+ if (node_was_null)
+ node = testing_append_commands_node;
+
+ xmlNode* e_tx = xmlNewNode(NULL, (const xmlChar*)"control_tx");
+
+ int direction_is_in = (rtype & 0x80) == 0x80;
+ sanei_xml_command_common_props(e_tx, rtype & 0x1f,
+ direction_is_in ? "IN" : "OUT");
+ sanei_xml_set_hex_attr(e_tx, "bmRequestType", rtype);
+ sanei_xml_set_hex_attr(e_tx, "bRequest", req);
+ sanei_xml_set_hex_attr(e_tx, "wValue", value);
+ sanei_xml_set_hex_attr(e_tx, "wIndex", index);
+ sanei_xml_set_hex_attr(e_tx, "wLength", len);
+
+ if (direction_is_in && data == NULL)
+ {
+ const int buf_size = 128;
+ char buf[buf_size];
+ snprintf(buf, buf_size, "(unknown read of size %d)", len);
+ xmlNode* e_content = xmlNewText((const xmlChar*)buf);
+ xmlAddChild(e_tx, e_content);
+ }
+ else
+ {
+ sanei_xml_set_hex_data(e_tx, (const char*)data, len);
+ }
+
+ node = sanei_xml_append_command(node, node_was_null, e_tx);
+
+ if (node_was_null)
+ testing_append_commands_node = node;
+}
+
+
+static SANE_Status
+sanei_usb_record_replace_control_msg(xmlNode* node,
+ SANE_Int dn, SANE_Int rtype, SANE_Int req,
+ SANE_Int value, SANE_Int index, SANE_Int len,
+ const SANE_Byte* data)
+{
+ if (!testing_development_mode)
+ return SANE_STATUS_IO_ERROR;
+
+ SANE_Status ret = SANE_STATUS_GOOD;
+ int direction_is_in = (rtype & 0x80) == 0x80;
+ if (direction_is_in)
+ {
+ testing_known_commands_input_failed = 1;
+ ret = SANE_STATUS_IO_ERROR;
+ }
+
+ testing_last_known_seq--;
+ sanei_usb_record_control_msg(node, dn, rtype, req, value, index, len, data);
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+ return ret;
+}
+
+static SANE_Status
+sanei_usb_replay_control_msg(SANE_Int dn, SANE_Int rtype, SANE_Int req,
+ SANE_Int value, SANE_Int index, SANE_Int len,
+ SANE_Byte* data)
+{
+ (void) dn;
+
+ if (testing_known_commands_input_failed)
+ return -1;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ int direction_is_in = (rtype & 0x80) == 0x80;
+ SANE_Byte* rdata = direction_is_in ? NULL : data;
+
+ if (sanei_xml_is_known_commands_end(node))
+ {
+ sanei_usb_record_control_msg(NULL, dn, rtype, req, value, index, len,
+ rdata);
+ if (direction_is_in)
+ {
+ testing_known_commands_input_failed = 1;
+ return SANE_STATUS_IO_ERROR;
+ }
+ return SANE_STATUS_GOOD;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"control_tx") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ return sanei_usb_record_replace_control_msg(node, dn, rtype, req, value,
+ index, len, rdata);
+ }
+
+ if (!sanei_usb_check_attr(node, "direction", direction_is_in ? "IN" : "OUT",
+ __func__) ||
+ !sanei_usb_check_attr_uint(node, "bmRequestType", rtype, __func__) ||
+ !sanei_usb_check_attr_uint(node, "bRequest", req, __func__) ||
+ !sanei_usb_check_attr_uint(node, "wValue", value, __func__) ||
+ !sanei_usb_check_attr_uint(node, "wIndex", index, __func__) ||
+ !sanei_usb_check_attr_uint(node, "wLength", len, __func__))
+ {
+ return sanei_usb_record_replace_control_msg(node, dn, rtype, req, value,
+ index, len, rdata);
+ }
+
+ size_t tx_data_size = 0;
+ char* tx_data = sanei_xml_get_hex_data(node, &tx_data_size);
+
+ if (direction_is_in)
+ {
+ if (tx_data_size != (size_t)len)
+ {
+ FAIL_TEST_TX(__func__, node,
+ "got different amount of data than wanted (%lu vs %lu)\n",
+ tx_data_size, (size_t)len);
+ free(tx_data);
+ return sanei_usb_record_replace_control_msg(node, dn, rtype, req,
+ value, index, len, rdata);
+ }
+ memcpy(data, tx_data, tx_data_size);
+ }
+ else
+ {
+ if (!sanei_usb_check_data_equal(node,
+ (const char*)data, len,
+ tx_data, tx_data_size, __func__))
+ {
+ free(tx_data);
+ return sanei_usb_record_replace_control_msg(node, dn, rtype, req,
+ value, index, len, rdata);
+ }
+ }
+ free(tx_data);
+ return SANE_STATUS_GOOD;
+}
+#endif
+
SANE_Status
sanei_usb_control_msg (SANE_Int dn, SANE_Int rtype, SANE_Int req,
SANE_Int value, SANE_Int index, SANE_Int len,
@@ -2426,6 +3947,16 @@ sanei_usb_control_msg (SANE_Int dn, SANE_Int rtype, SANE_Int req,
if (!(rtype & 0x80) && debug_level > 10)
print_buffer (data, len);
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+#if WITH_USB_RECORD_REPLAY
+ return sanei_usb_replay_control_msg(dn, rtype, req, value, index, len,
+ data);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
if (devices[dn].method == sanei_usb_method_scanner_driver)
{
#if defined(__linux__)
@@ -2537,9 +4068,141 @@ sanei_usb_control_msg (SANE_Int dn, SANE_Int rtype, SANE_Int req,
devices[dn].method);
return SANE_STATUS_UNSUPPORTED;
}
+
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ // TODO: record in the error code path too
+ sanei_usb_record_control_msg(NULL, dn, rtype, req, value, index, len,
+ data);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
return SANE_STATUS_GOOD;
}
+#if WITH_USB_RECORD_REPLAY
+static void sanei_usb_record_read_int(xmlNode* node,
+ SANE_Int dn, SANE_Byte* buffer,
+ size_t size, ssize_t read_size)
+{
+ (void) size;
+
+ int node_was_null = node == NULL;
+ if (node_was_null)
+ node = testing_append_commands_node;
+
+ xmlNode* e_tx = xmlNewNode(NULL, (const xmlChar*)"interrupt_tx");
+
+ sanei_xml_command_common_props(e_tx, devices[dn].int_in_ep & 0x0f, "IN");
+
+ if (buffer == NULL)
+ {
+ const int buf_size = 128;
+ char buf[buf_size];
+ snprintf(buf, buf_size, "(unknown read of wanted size %ld)", read_size);
+ xmlNode* e_content = xmlNewText((const xmlChar*)buf);
+ xmlAddChild(e_tx, e_content);
+ }
+ else
+ {
+ if (read_size >= 0)
+ {
+ sanei_xml_set_hex_data(e_tx, (const char*)buffer, read_size);
+ }
+ else
+ {
+ xmlNewProp(e_tx, (const xmlChar*)"error", (const xmlChar*)"timeout");
+ }
+ }
+
+ node = sanei_xml_append_command(node, node_was_null, e_tx);
+
+ if (node_was_null)
+ testing_append_commands_node = node;
+}
+
+static void sanei_usb_record_replace_read_int(xmlNode* node,
+ SANE_Int dn, SANE_Byte* buffer,
+ size_t size, size_t read_size)
+{
+ if (!testing_development_mode)
+ return;
+ testing_known_commands_input_failed = 1;
+ testing_last_known_seq--;
+ sanei_usb_record_read_int(node, dn, buffer, size, read_size);
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+}
+
+static int sanei_usb_replay_read_int(SANE_Int dn, SANE_Byte* buffer,
+ size_t size)
+{
+ if (testing_known_commands_input_failed)
+ return -1;
+
+ size_t wanted_size = size;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return -1;
+ }
+
+ if (sanei_xml_is_known_commands_end(node))
+ {
+ sanei_usb_record_read_int(NULL, dn, NULL, 0, size);
+ testing_known_commands_input_failed = 1;
+ return -1;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"interrupt_tx") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ sanei_usb_record_replace_read_int(node, dn, NULL, 0, size);
+ return -1;
+ }
+
+ if (!sanei_usb_check_attr(node, "direction", "IN", __func__))
+ {
+ sanei_usb_record_replace_read_int(node, dn, NULL, 0, size);
+ return -1;
+ }
+
+ if (!sanei_usb_check_attr_uint(node, "endpoint_number",
+ devices[dn].int_in_ep & 0x0f,
+ __func__))
+ {
+ sanei_usb_record_replace_read_int(node, dn, NULL, 0, size);
+ return -1;
+ }
+
+ size_t tx_data_size = 0;
+ char* tx_data = sanei_xml_get_hex_data(node, &tx_data_size);
+
+ if (tx_data_size > wanted_size)
+ {
+ FAIL_TEST_TX(__func__, node,
+ "got more data than wanted (%lu vs %lu)\n",
+ tx_data_size, wanted_size);
+ sanei_usb_record_replace_read_int(node, dn, NULL, 0, size);
+ free(tx_data);
+ return -1;
+ }
+
+ memcpy((char*) buffer, tx_data, tx_data_size);
+ free(tx_data);
+ return tx_data_size;
+}
+#endif // WITH_USB_RECORD_REPLAY
+
SANE_Status
sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
{
@@ -2562,7 +4225,16 @@ sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
DBG (5, "sanei_usb_read_int: trying to read %lu bytes\n",
(unsigned long) *size);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+#if WITH_USB_RECORD_REPLAY
+ read_size = sanei_usb_replay_read_int(dn, buffer, *size);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
DBG (1, "sanei_usb_read_int: access method %d not implemented\n",
devices[dn].method);
@@ -2658,8 +4330,22 @@ sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ sanei_usb_record_read_int(NULL, dn, buffer, *size, read_size);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
if (read_size < 0)
{
+ *size = 0;
+ if (testing_mode != sanei_usb_testing_mode_disabled)
+ return SANE_STATUS_IO_ERROR;
+
#ifdef HAVE_LIBUSB_LEGACY
if (devices[dn].method == sanei_usb_method_libusb)
if (stalled)
@@ -2669,7 +4355,6 @@ sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
if (stalled)
libusb_clear_halt (devices[dn].lu_handle, devices[dn].int_in_ep);
#endif
- *size = 0;
return SANE_STATUS_IO_ERROR;
}
if (read_size == 0)
@@ -2687,6 +4372,58 @@ sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
return SANE_STATUS_GOOD;
}
+#if WITH_USB_RECORD_REPLAY
+static SANE_Status sanei_usb_replay_set_configuration(SANE_Int dn,
+ SANE_Int configuration)
+{
+ (void) dn;
+
+ xmlNode* node = sanei_xml_get_next_tx_node();
+ if (node == NULL)
+ {
+ FAIL_TEST(__func__, "no more transactions\n");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ sanei_xml_record_seq(node);
+ sanei_xml_break_if_needed(node);
+
+ if (xmlStrcmp(node->name, (const xmlChar*)"control_tx") != 0)
+ {
+ FAIL_TEST_TX(__func__, node, "unexpected transaction type %s\n",
+ (const char*) node->name);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (!sanei_usb_check_attr(node, "direction", "OUT", __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ if (!sanei_usb_check_attr_uint(node, "bmRequestType", 0, __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ if (!sanei_usb_check_attr_uint(node, "bRequest", 9, __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ if (!sanei_usb_check_attr_uint(node, "wValue", configuration, __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ if (!sanei_usb_check_attr_uint(node, "wIndex", 0, __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ if (!sanei_usb_check_attr_uint(node, "wLength", 0, __func__))
+ return SANE_STATUS_IO_ERROR;
+
+ return SANE_STATUS_GOOD;
+}
+
+static void sanei_usb_record_set_configuration(SANE_Int dn,
+ SANE_Int configuration)
+{
+ (void) dn; (void) configuration;
+ // TODO
+}
+#endif // WITH_USB_RECORD_REPLAY
+
SANE_Status
sanei_usb_set_configuration (SANE_Int dn, SANE_Int configuration)
{
@@ -2700,7 +4437,26 @@ sanei_usb_set_configuration (SANE_Int dn, SANE_Int configuration)
DBG (5, "sanei_usb_set_configuration: configuration = %d\n", configuration);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+#if WITH_USB_RECORD_REPLAY
+ sanei_usb_record_set_configuration(dn, configuration);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+#if WITH_USB_RECORD_REPLAY
+ return sanei_usb_replay_set_configuration(dn, configuration);
+#else
+ DBG (1, "USB record-replay mode support is missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
#if defined(__linux__)
return SANE_STATUS_GOOD;
@@ -2770,7 +4526,11 @@ sanei_usb_claim_interface (SANE_Int dn, SANE_Int interface_number)
DBG (5, "sanei_usb_claim_interface: interface_number = %d\n", interface_number);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ return SANE_STATUS_GOOD;
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
#if defined(__linux__)
return SANE_STATUS_GOOD;
@@ -2837,7 +4597,11 @@ sanei_usb_release_interface (SANE_Int dn, SANE_Int interface_number)
}
DBG (5, "sanei_usb_release_interface: interface_number = %d\n", interface_number);
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ return SANE_STATUS_GOOD;
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
#if defined(__linux__)
return SANE_STATUS_GOOD;
@@ -2903,7 +4667,11 @@ sanei_usb_set_altinterface (SANE_Int dn, SANE_Int alternate)
devices[dn].alt_setting = alternate;
- if (devices[dn].method == sanei_usb_method_scanner_driver)
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ return SANE_STATUS_GOOD;
+ }
+ else if (devices[dn].method == sanei_usb_method_scanner_driver)
{
#if defined(__linux__)
return SANE_STATUS_GOOD;
@@ -2955,6 +4723,25 @@ sanei_usb_set_altinterface (SANE_Int dn, SANE_Int alternate)
}
}
+static SANE_Status
+sanei_usb_replay_get_descriptor(SANE_Int dn,
+ struct sanei_usb_dev_descriptor *desc)
+{
+ (void) dn;
+ (void) desc;
+ return SANE_STATUS_UNSUPPORTED;
+ // ZZTODO
+}
+
+static void
+sanei_usb_record_get_descriptor(SANE_Int dn,
+ struct sanei_usb_dev_descriptor *desc)
+{
+ (void) dn;
+ (void) desc;
+ // ZZTODO
+}
+
extern SANE_Status
sanei_usb_get_descriptor( SANE_Int dn,
struct sanei_usb_dev_descriptor __sane_unused__
@@ -2968,6 +4755,11 @@ sanei_usb_get_descriptor( SANE_Int dn,
return SANE_STATUS_INVAL;
}
+ if (testing_mode == sanei_usb_testing_mode_replay)
+ {
+ return sanei_usb_replay_get_descriptor(dn, desc);
+ }
+
DBG (5, "sanei_usb_get_descriptor\n");
#ifdef HAVE_LIBUSB_LEGACY
{
@@ -3013,5 +4805,11 @@ sanei_usb_get_descriptor( SANE_Int dn,
return SANE_STATUS_UNSUPPORTED;
}
#endif /* not HAVE_LIBUSB_LEGACY && not HAVE_LIBUSB */
+
+ if (testing_mode == sanei_usb_testing_mode_record)
+ {
+ sanei_usb_record_get_descriptor(dn, desc);
+ }
+
return SANE_STATUS_GOOD;
}