diff options
Diffstat (limited to 'sanei')
| -rw-r--r-- | sanei/Makefile.am | 2 | ||||
| -rw-r--r-- | sanei/sanei_init_debug.c | 10 | ||||
| -rw-r--r-- | sanei/sanei_scsi.c | 20 | ||||
| -rw-r--r-- | sanei/sanei_usb.c | 1822 | ||||
| -rw-r--r-- | sanei/sanei_wire.c | 2 | 
5 files changed, 1831 insertions, 25 deletions
| diff --git a/sanei/Makefile.am b/sanei/Makefile.am index a197343..46d3ff4 100644 --- a/sanei/Makefile.am +++ b/sanei/Makefile.am @@ -5,7 +5,7 @@  ##  included LICENSE file for license information.  AM_CPPFLAGS += -I. -I$(srcdir) -I$(top_builddir)/include \ - -I$(top_srcdir)/include $(USB_CFLAGS) + -I$(top_srcdir)/include $(USB_CFLAGS) $(XML_CFLAGS)  noinst_LTLIBRARIES = libsanei.la diff --git a/sanei/sanei_init_debug.c b/sanei/sanei_init_debug.c index 946aee9..3cde74e 100644 --- a/sanei/sanei_init_debug.c +++ b/sanei/sanei_init_debug.c @@ -58,6 +58,8 @@  #include <sys/socket.h>  #endif  #include <sys/stat.h> +#include <time.h> +#include <sys/time.h>  #ifdef HAVE_OS2_H  # define INCL_DOS @@ -148,7 +150,13 @@ sanei_debug_msg  	}        else  	{ -	  fprintf (stderr, "[%s] ", be); +          struct timeval tv; +          struct tm *t; + +          gettimeofday (&tv, NULL); +          t = localtime (&tv.tv_sec); + +          fprintf (stderr, "[%02d:%02d:%02d.%06ld] [%s] ", t->tm_hour, t->tm_min, t->tm_sec, tv.tv_usec, be);            vfprintf (stderr, fmt, ap);  	} diff --git a/sanei/sanei_scsi.c b/sanei/sanei_scsi.c index ca3dbb0..b69f78f 100644 --- a/sanei/sanei_scsi.c +++ b/sanei/sanei_scsi.c @@ -615,7 +615,7 @@ open_aspi (void)    DBG (1, "OS/2: unique id is    '%s'\n", PSRBlock->u.inq.unique_id);    strcpy (tmpAspi, "asXXXXXX"); -  mktemp (tmpAspi); +  mkstemp (tmpAspi);    DBG (2, "open_aspi: open temporary file '%s'\n", tmpAspi);    tmp = fopen (tmpAspi, "w");    if (!tmp) @@ -3271,8 +3271,8 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,      ccb = cam_getccb (dev);      /* Build the CCB */ -    bzero (&(&ccb->ccb_h)[1], sizeof (struct ccb_scsiio)); -    bcopy (cmd, &ccb->csio.cdb_io.cdb_bytes, cmd_size); +    memset (&(&ccb->ccb_h)[1], 0, sizeof (struct ccb_scsiio)); +    memcpy (&ccb->csio.cdb_io.cdb_bytes, cmd, cmd_size);      /*       * Set the data direction flags. @@ -3368,7 +3368,7 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,      int retval = 0;      /* build ccb for device match */ -    bzero (&cdm, sizeof (cdm)); +    memset (&cdm, 0, sizeof (cdm));      cdm.ccb_h.func_code = XPT_DEV_MATCH;      /* result buffer */ @@ -3454,7 +3454,7 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,        }      /* build ccb for device match */ -    bzero (&cdm, sizeof (cdm)); +    memset (&cdm, 0, sizeof (cdm));      cdm.ccb_h.func_code = XPT_DEV_MATCH;      /* result buffer */ @@ -4449,7 +4449,7 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,  	memcpy (databuf, (u_char *) src, src_size);        } -    bzero (sensebuf, 128); +    memset (sensebuf, 0, 128);      /*       * Do SCSI request... @@ -5358,7 +5358,7 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,      memcpy (&cdb.cdb, cmd, cmd_size);      if (dst && dst_size)        { -	bzero (dst, *dst_size); +	memset (dst, 0, *dst_size);  	range.address = (IOVirtualAddress) dst;  	range.length = *dst_size;  	transferCount = *dst_size; @@ -5699,7 +5699,7 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,  	DBG (6, "isRead dst_size:%ld\n", *dst_size);  	/* Zero the buffer. */ -	bzero (dst, *dst_size); +	memset (dst, 0, *dst_size);  	/* Configure the virtual range for the buffer. */  	range.address = (long) dst; @@ -5722,8 +5722,8 @@ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,      /* zero the senseData and CDB */ -    bzero (&senseData, sizeof (senseData)); -    bzero (cdb, sizeof (cdb)); +    memset (&senseData, 0, sizeof (senseData)); +    memset (cdb, 0, sizeof (cdb));      /* copy the command data */      memcpy (cdb, cmd, cmd_size); 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;  } diff --git a/sanei/sanei_wire.c b/sanei/sanei_wire.c index 32cd1a0..85021d1 100644 --- a/sanei/sanei_wire.c +++ b/sanei/sanei_wire.c @@ -150,7 +150,7 @@ sanei_w_space (Wire * w, size_t howmuch)  }  void -sanei_w_void (Wire * w) +sanei_w_void (Wire * w, void __sane_unused__ * v)  {    DBG (3, "sanei_w_void: wire %d (void debug output)\n", w->io.fd);  } | 
