diff options
author | Till Kamppeter <till.kamppeter@gmail.com> | 2017-04-20 17:27:03 -0300 |
---|---|---|
committer | Till Kamppeter <till.kamppeter@gmail.com> | 2017-04-20 17:27:03 -0300 |
commit | 3cae655d6f7feaa8e5620f28f38d905f8c3d2838 (patch) | |
tree | 7ac3ce119c709d0d729509637e22b0ec8dedf941 | |
parent | a78b712cf53326a060bdac439c76938d86e4b9e3 (diff) |
White space clean-up in C source code files
-rw-r--r-- | src/dnssd.c | 99 | ||||
-rw-r--r-- | src/dnssd.h | 8 | ||||
-rw-r--r-- | src/http.c | 1017 | ||||
-rw-r--r-- | src/http.h | 44 | ||||
-rw-r--r-- | src/ippusbxd.c | 955 | ||||
-rw-r--r-- | src/logging.c | 18 | ||||
-rw-r--r-- | src/logging.h | 8 | ||||
-rw-r--r-- | src/options.h | 48 | ||||
-rw-r--r-- | src/tcp.c | 560 | ||||
-rw-r--r-- | src/tcp.h | 10 | ||||
-rw-r--r-- | src/usb.c | 1415 | ||||
-rw-r--r-- | src/usb.h | 46 |
12 files changed, 2102 insertions, 2126 deletions
diff --git a/src/dnssd.c b/src/dnssd.c index 2b168c5..5db8e90 100644 --- a/src/dnssd.c +++ b/src/dnssd.c @@ -27,10 +27,9 @@ */ static void -dnssd_callback( - AvahiEntryGroup *g, /* I - Service */ - AvahiEntryGroupState state, /* I - Registration state */ - void *context) /* I - Printer */ +dnssd_callback(AvahiEntryGroup *g, /* I - Service */ + AvahiEntryGroupState state, /* I - Registration state */ + void *context) /* I - Printer */ { (void)context; @@ -65,10 +64,9 @@ dnssd_callback( */ static void -dnssd_client_cb( - AvahiClient *c, /* I - Client */ - AvahiClientState state, /* I - Current state */ - void *userdata) /* I - User data (unused) */ +dnssd_client_cb(AvahiClient *c, /* I - Client */ + AvahiClientState state, /* I - Current state */ + void *userdata) /* I - User data (unused) */ { (void)userdata; int error; /* Error code, if any */ @@ -76,49 +74,48 @@ dnssd_client_cb( if (!c) return; - switch (state) - { - default : - NOTE("Ignore Avahi state %d.", state); - break; - - case AVAHI_CLIENT_CONNECTING: - NOTE("Waiting for Avahi server."); - break; - - case AVAHI_CLIENT_S_RUNNING: - NOTE("Avahi server connection got available, registering printer."); - dnssd_register(c); - break; - - case AVAHI_CLIENT_S_REGISTERING: - case AVAHI_CLIENT_S_COLLISION: - NOTE("Dropping printer registration because of possible host name change."); - if (g_options.dnssd_data->ipp_ref) - avahi_entry_group_reset(g_options.dnssd_data->ipp_ref); - break; - - case AVAHI_CLIENT_FAILURE: - if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { - NOTE("Avahi server disappeared, unregistering printer"); - dnssd_unregister(); - /* Renewing client */ - if (g_options.dnssd_data->DNSSDClient) - avahi_client_free(g_options.dnssd_data->DNSSDClient); - if ((g_options.dnssd_data->DNSSDClient = - avahi_client_new(avahi_threaded_poll_get - (g_options.dnssd_data->DNSSDMaster), - AVAHI_CLIENT_NO_FAIL, - dnssd_client_cb, NULL, &error)) == NULL) { - ERR("Error: Unable to initialize DNS-SD client."); - g_options.terminate = 1; - } - } else { - ERR("Avahi server connection failure: %s", - avahi_strerror(avahi_client_errno(c))); - g_options.terminate = 1; - } - break; + switch (state) { + default : + NOTE("Ignore Avahi state %d.", state); + break; + + case AVAHI_CLIENT_CONNECTING: + NOTE("Waiting for Avahi server."); + break; + + case AVAHI_CLIENT_S_RUNNING: + NOTE("Avahi server connection got available, registering printer."); + dnssd_register(c); + break; + + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_COLLISION: + NOTE("Dropping printer registration because of possible host name change."); + if (g_options.dnssd_data->ipp_ref) + avahi_entry_group_reset(g_options.dnssd_data->ipp_ref); + break; + + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + NOTE("Avahi server disappeared, unregistering printer"); + dnssd_unregister(); + /* Renewing client */ + if (g_options.dnssd_data->DNSSDClient) + avahi_client_free(g_options.dnssd_data->DNSSDClient); + if ((g_options.dnssd_data->DNSSDClient = + avahi_client_new(avahi_threaded_poll_get + (g_options.dnssd_data->DNSSDMaster), + AVAHI_CLIENT_NO_FAIL, + dnssd_client_cb, NULL, &error)) == NULL) { + ERR("Error: Unable to initialize DNS-SD client."); + g_options.terminate = 1; + } + } else { + ERR("Avahi server connection failure: %s", + avahi_strerror(avahi_client_errno(c))); + g_options.terminate = 1; + } + break; } } diff --git a/src/dnssd.h b/src/dnssd.h index fb1b79f..37e977a 100644 --- a/src/dnssd.h +++ b/src/dnssd.h @@ -25,7 +25,7 @@ typedef struct dnssd_s { AvahiEntryGroup *ipp_ref; } dnssd_t; -int dnssd_init(); -void dnssd_shutdown(); -int dnssd_register(); -void dnssd_unregister(); +int dnssd_init(); +void dnssd_shutdown(); +int dnssd_register(); +void dnssd_unregister(); @@ -29,585 +29,580 @@ struct http_message_t *http_message_new() { - struct http_message_t *msg = calloc(1, sizeof(*msg)); - if (msg == NULL) { - ERR("failed to alloc space for http message"); - return NULL; - } + struct http_message_t *msg = calloc(1, sizeof(*msg)); + if (msg == NULL) { + ERR("failed to alloc space for http message"); + return NULL; + } - msg->spare_capacity = 0; - msg->spare_filled = 0; - msg->spare_buffer = NULL; + msg->spare_capacity = 0; + msg->spare_filled = 0; + msg->spare_buffer = NULL; - return msg; + return msg; } void message_free(struct http_message_t *msg) { - free(msg->spare_buffer); - free(msg); + free(msg->spare_buffer); + free(msg); } static void packet_check_completion(struct http_packet_t *pkt) { - struct http_message_t *msg = pkt->parent_message; - // Msg full - if (msg->claimed_size && msg->received_size >= msg->claimed_size) { - msg->is_completed = 1; - NOTE("http: Message completed: Received size >= claimed size"); - - // Sanity check - if (msg->spare_filled > 0) - ERR_AND_EXIT("Msg spare not empty upon completion"); - } - - // Pkt full - if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) { - pkt->is_completed = 1; - NOTE("http: Packet completed: Packet full"); - } - - // Pkt over capacity - if (pkt->filled_size > pkt->buffer_capacity) { - // Santiy check - ERR_AND_EXIT("Overflowed packet buffer"); - } + struct http_message_t *msg = pkt->parent_message; + // Msg full + if (msg->claimed_size && msg->received_size >= msg->claimed_size) { + msg->is_completed = 1; + NOTE("http: Message completed: Received size >= claimed size"); + + // Sanity check + if (msg->spare_filled > 0) + ERR_AND_EXIT("Msg spare not empty upon completion"); + } + + // Pkt full + if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) { + pkt->is_completed = 1; + NOTE("http: Packet completed: Packet full"); + } + + // Pkt over capacity + if (pkt->filled_size > pkt->buffer_capacity) { + // Santiy check + ERR_AND_EXIT("Overflowed packet buffer"); + } } static int doesMatch(const char *matcher, size_t matcher_len, const uint8_t *key, size_t key_len) { - for (size_t i = 0; i < matcher_len; i++) - if (i >= key_len || matcher[i] != key[i]) - return 0; - return 1; + for (size_t i = 0; i < matcher_len; i++) + if (i >= key_len || matcher[i] != key[i]) + return 0; + return 1; } static int inspect_header_field(struct http_packet_t *pkt, size_t header_size, char *key, size_t key_size) { - // Find key - uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); - if (pos == NULL) - return -1; - - // Find first digit - size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; - while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) - ++number_pos; - - // Find next non-digit - size_t number_end = number_pos; - while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) - ++number_end; - - // Failed to find next non-digit - // header field might be broken - if (number_end >= pkt->filled_size) - return -1; - - // Temporary stringification of buffer for atoi() - uint8_t original_char = pkt->buffer[number_end]; - pkt->buffer[number_end] = '\0'; - int val = atoi((const char *)(pkt->buffer + number_pos)); - - // Restore buffer - pkt->buffer[number_end] = original_char; - return val; + // Find key + uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); + if (pos == NULL) + return -1; + + // Find first digit + size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; + while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) + ++number_pos; + + // Find next non-digit + size_t number_end = number_pos; + while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) + ++number_end; + + // Failed to find next non-digit + // header field might be broken + if (number_end >= pkt->filled_size) + return -1; + + // Temporary stringification of buffer for atoi() + uint8_t original_char = pkt->buffer[number_end]; + pkt->buffer[number_end] = '\0'; + int val = atoi((const char *)(pkt->buffer + number_pos)); + + // Restore buffer + pkt->buffer[number_end] = original_char; + return val; } static void packet_store_excess(struct http_packet_t *pkt) { - struct http_message_t *msg = pkt->parent_message; - if (msg->spare_buffer != NULL) - ERR_AND_EXIT("Do not store excess to non-empty packet"); - - if (pkt->expected_size >= pkt->filled_size) - ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); - - size_t spare_size = pkt->filled_size - pkt->expected_size; - size_t non_spare = pkt->expected_size; - NOTE("HTTP: Storing %d bytes of excess", spare_size); - - // Align to BUFFER_STEP - size_t needed_size = 0; - needed_size += spare_size / BUFFER_STEP; - needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; - - if (msg->spare_buffer == NULL) { - uint8_t *buffer = calloc(1, needed_size); - if (buffer == NULL) - ERR_AND_EXIT("Failed to alloc msg spare buffer"); - - msg->spare_buffer = buffer; - } - - memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); - pkt->filled_size = non_spare; - - msg->spare_capacity = needed_size; - msg->spare_filled = spare_size; + struct http_message_t *msg = pkt->parent_message; + if (msg->spare_buffer != NULL) + ERR_AND_EXIT("Do not store excess to non-empty packet"); + + if (pkt->expected_size >= pkt->filled_size) + ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); + + size_t spare_size = pkt->filled_size - pkt->expected_size; + size_t non_spare = pkt->expected_size; + NOTE("HTTP: Storing %d bytes of excess", spare_size); + + // Align to BUFFER_STEP + size_t needed_size = 0; + needed_size += spare_size / BUFFER_STEP; + needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; + + if (msg->spare_buffer == NULL) { + uint8_t *buffer = calloc(1, needed_size); + if (buffer == NULL) + ERR_AND_EXIT("Failed to alloc msg spare buffer"); + msg->spare_buffer = buffer; + } + + memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); + pkt->filled_size = non_spare; + + msg->spare_capacity = needed_size; + msg->spare_filled = spare_size; } static void packet_take_spare(struct http_packet_t *pkt) { - struct http_message_t *msg = pkt->parent_message; - if (msg->spare_filled == 0) - return; + struct http_message_t *msg = pkt->parent_message; + if (msg->spare_filled == 0) + return; - if (msg->spare_buffer == NULL) - return; + if (msg->spare_buffer == NULL) + return; - if (pkt->filled_size > 0) - ERR_AND_EXIT("pkt should be empty when loading msg spare"); + if (pkt->filled_size > 0) + ERR_AND_EXIT("pkt should be empty when loading msg spare"); - // Take message's buffer - size_t msg_size = msg->spare_capacity; - size_t msg_filled = msg->spare_filled; - uint8_t *msg_buffer = msg->spare_buffer; + // Take message's buffer + size_t msg_size = msg->spare_capacity; + size_t msg_filled = msg->spare_filled; + uint8_t *msg_buffer = msg->spare_buffer; - pkt->buffer_capacity = msg_size; - pkt->filled_size = msg_filled; - pkt->buffer = msg_buffer; + pkt->buffer_capacity = msg_size; + pkt->filled_size = msg_filled; + pkt->buffer = msg_buffer; - msg->spare_capacity = 0; - msg->spare_filled = 0; - msg->spare_buffer = NULL; + msg->spare_capacity = 0; + msg->spare_filled = 0; + msg->spare_buffer = NULL; } static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) { - // NOTE: - // chunks can have trailers which are - // tacked on http header fields. - // NOTE: - // chunks may also have extensions. - // No one uses or supports them. - - // Find end of size string - if (pkt->filled_size >= SSIZE_MAX) - ERR_AND_EXIT("Buffer beyond sane size"); - - ssize_t max = (ssize_t) pkt->filled_size; - ssize_t size_end = -1; - ssize_t miniheader_end = -1; - ssize_t delimiter_start = -1; - for (ssize_t i = 0; i < max; i++) { - - uint8_t *buf = pkt->buffer; - if (size_end < 0) { - // No extension - if (i + 1 < max && ( - buf[i] == '\r' && // CR - buf[i + 1] == '\n')// LF - ) { - size_end = i + 1; - miniheader_end = size_end; - delimiter_start = i; - break; - } - - // No extension - if (buf[i] == '\n') // LF - { - size_end = i; - miniheader_end = size_end; - delimiter_start = i; - break; - } - - // Has extensions - if (buf[i] == ';') - { - size_end = i; - continue; - } - } - if (miniheader_end < 0) { - if (i + 1 < max && ( - buf[i] == '\r' && // CR - buf[i + 1] == '\n')// LF - ) { - miniheader_end = i + 1; - delimiter_start = i; - break; - } - - if (buf[i] == '\n') // LF - { - miniheader_end = i; - delimiter_start = i; - break; - } - } - } - - if (miniheader_end < 0) { - // NOTE: knowing just the size field - // is not enough since the extensions - // are not included in the size - NOTE("failed to find chunk mini-header so far"); - return -1; - } - - // Temporary stringification for strtol() - uint8_t original_char = *(pkt->buffer + size_end); - *(pkt->buffer + size_end) = '\0'; - size_t size = strtoul((char *)pkt->buffer, NULL, 16); - NOTE("Chunk size raw: %s", pkt->buffer); - *(pkt->buffer + size_end) = original_char; - if (size > SSIZE_MAX) - ERR_AND_EXIT("chunk size is insane"); - - if (size > 0) { - // Regular chunk - ssize_t chunk_size = (ssize_t) size; // Chunk body - chunk_size += miniheader_end + 1; // Mini-header - chunk_size += 2; // Trailing CRLF - NOTE("HTTP: Chunk size: %lu", chunk_size); - return (ssize_t) chunk_size; - } - - // Terminator chunk - // May have trailers in body - ssize_t full_size = -1; - for (ssize_t i = delimiter_start; i < max; i++) { - uint8_t *buf = pkt->buffer; - if (i + 3 < max && ( - buf[i] == '\r' && // CR - buf[i + 1] == '\n' && // LF - buf[i + 2] == '\r' && // CR - buf[i + 3] == '\n') // LF - ) { - full_size = i + 4; - break; - } - - if (i + 1 < max && - buf[i] == '\n' && // LF - buf[i + 1] == '\n') // LF - { - full_size = i + 2; - break; - } - } - - if (full_size < 0) { - NOTE("Chunk miniheader present but body incomplete"); - return -1; - } - - NOTE("Found end chunked packet"); - pkt->parent_message->is_completed = 1; - pkt->is_completed = 1; - return full_size; + // NOTE: + // chunks can have trailers which are + // tacked on http header fields. + // NOTE: + // chunks may also have extensions. + // No one uses or supports them. + + // Find end of size string + if (pkt->filled_size >= SSIZE_MAX) + ERR_AND_EXIT("Buffer beyond sane size"); + + ssize_t max = (ssize_t) pkt->filled_size; + ssize_t size_end = -1; + ssize_t miniheader_end = -1; + ssize_t delimiter_start = -1; + for (ssize_t i = 0; i < max; i++) { + + uint8_t *buf = pkt->buffer; + if (size_end < 0) { + // No extension + if (i + 1 < max && + (buf[i] == '\r' && // CR + buf[i + 1] == '\n')) { // LF + size_end = i + 1; + miniheader_end = size_end; + delimiter_start = i; + break; + } + + // No extension + if (buf[i] == '\n') { // LF + size_end = i; + miniheader_end = size_end; + delimiter_start = i; + break; + } + + // Has extensions + if (buf[i] == ';') { + size_end = i; + continue; + } + } + if (miniheader_end < 0) { + if (i + 1 < max && + (buf[i] == '\r' && // CR + buf[i + 1] == '\n')) { // LF + miniheader_end = i + 1; + delimiter_start = i; + break; + } + + if (buf[i] == '\n') { // LF + miniheader_end = i; + delimiter_start = i; + break; + } + } + } + + if (miniheader_end < 0) { + // NOTE: knowing just the size field + // is not enough since the extensions + // are not included in the size + NOTE("failed to find chunk mini-header so far"); + return -1; + } + + // Temporary stringification for strtol() + uint8_t original_char = *(pkt->buffer + size_end); + *(pkt->buffer + size_end) = '\0'; + size_t size = strtoul((char *)pkt->buffer, NULL, 16); + NOTE("Chunk size raw: %s", pkt->buffer); + *(pkt->buffer + size_end) = original_char; + if (size > SSIZE_MAX) + ERR_AND_EXIT("chunk size is insane"); + + if (size > 0) { + // Regular chunk + ssize_t chunk_size = (ssize_t) size; // Chunk body + chunk_size += miniheader_end + 1; // Mini-header + chunk_size += 2; // Trailing CRLF + NOTE("HTTP: Chunk size: %lu", chunk_size); + return (ssize_t) chunk_size; + } + + // Terminator chunk + // May have trailers in body + ssize_t full_size = -1; + for (ssize_t i = delimiter_start; i < max; i++) { + uint8_t *buf = pkt->buffer; + if (i + 3 < max && + (buf[i] == '\r' && // CR + buf[i + 1] == '\n' && // LF + buf[i + 2] == '\r' && // CR + buf[i + 3] == '\n')) { // LF + full_size = i + 4; + break; + } + + if (i + 1 < max && + buf[i] == '\n' && // LF + buf[i + 1] == '\n') { // LF + full_size = i + 2; + break; + } + } + + if (full_size < 0) { + NOTE("Chunk miniheader present but body incomplete"); + return -1; + } + + NOTE("Found end chunked packet"); + pkt->parent_message->is_completed = 1; + pkt->is_completed = 1; + return full_size; } static ssize_t packet_get_header_size(struct http_packet_t *pkt) { - if (pkt->header_size != 0) - goto found; - - /* RFC2616 recomends we match newline on \n despite full - * complience requires the message to use only \r\n - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 - */ - - // Find header - for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { - // two \r\n pairs - if ((i + 3) < pkt->filled_size && - '\r' == pkt->buffer[i] && - '\n' == pkt->buffer[i + 1] && - '\r' == pkt->buffer[i + 2] && - '\n' == pkt->buffer[i + 3]) { - pkt->header_size = i + 4; - goto found; - } - - // two \n pairs - if ((i + 1) < pkt->filled_size && - '\n' == pkt->buffer[i] && - '\n' == pkt->buffer[i + 1]) { - pkt->header_size = i + 2; - goto found; - } - } - - return -1; - -found: - return (ssize_t) pkt->header_size; + if (pkt->header_size != 0) + goto found; + + /* + * RFC2616 recomends we match newline on \n despite full + * complience requires the message to use only \r\n + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 + */ + + // Find header + for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { + // two \r\n pairs + if ((i + 3) < pkt->filled_size && + '\r' == pkt->buffer[i] && + '\n' == pkt->buffer[i + 1] && + '\r' == pkt->buffer[i + 2] && + '\n' == pkt->buffer[i + 3]) { + pkt->header_size = i + 4; + goto found; + } + + // two \n pairs + if ((i + 1) < pkt->filled_size && + '\n' == pkt->buffer[i] && + '\n' == pkt->buffer[i + 1]) { + pkt->header_size = i + 2; + goto found; + } + } + + return -1; + + found: + return (ssize_t) pkt->header_size; } enum http_request_t packet_find_type(struct http_packet_t *pkt) { - enum http_request_t type = HTTP_UNSET; - size_t size = 0; - /* Valid methods for determining http request - * size are defined by W3 in RFC2616 section 4.4 - * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 - */ - - /* This function attempts to find what method this - * packet would use. This is only possible in specific case: - * 1. if the request uses method 1 we can check the http - * request type. We must be called on a packet which - * has the full header. - * 2. if the request uses method 2 we need the full header - * but a simple network-byte-order-aware string search - * works. This function does not work if called with - * a chunked transport's sub-packet. - * 3. if the request uses method 3 we again perform the - * string search. - * - * All cases require the packat to contain the full header. - */ - - ssize_t header_size_raw = packet_get_header_size(pkt); - if (header_size_raw < 0) { - // We don't have the header yet - goto do_ret; - } - size_t header_size = (size_t) header_size_raw; - - // Try Transfer-Encoding Chunked - char xfer_encode_str[] = "Transfer-Encoding: chunked"; - size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; - uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, - xfer_encode_str, - xfer_encode_str_size); - if (xfer_encode_pos != NULL) { - size = 0; - type = HTTP_CHUNKED; - goto do_ret; - } - - // Try Content-Length - char content_length_str[] = "Content-Length: "; - ssize_t contlen_size = inspect_header_field(pkt, header_size, - content_length_str, sizeof(content_length_str) - 1); - if (contlen_size >= 0) { - size = (size_t) contlen_size + header_size; - type = HTTP_CONTENT_LENGTH; - goto do_ret; - } - - // GET requests (start with GET) or answers from the server (start - // with HTTP) - if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) || - doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) { - size = header_size; - type = HTTP_HEADER_ONLY; - goto do_ret; - } - - // No size was detectable yet header was found - type = HTTP_UNKNOWN; - size = 0; - -do_ret: - pkt->parent_message->claimed_size = size; - pkt->parent_message->type = type; - return type; + enum http_request_t type = HTTP_UNSET; + size_t size = 0; + /* + * Valid methods for determining http request + * size are defined by W3 in RFC2616 section 4.4 + * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + */ + + /* + * This function attempts to find what method this + * packet would use. This is only possible in specific case: + * 1. if the request uses method 1 we can check the http + * request type. We must be called on a packet which + * has the full header. + * 2. if the request uses method 2 we need the full header + * but a simple network-byte-order-aware string search + * works. This function does not work if called with + * a chunked transport's sub-packet. + * 3. if the request uses method 3 we again perform the + * string search. + * + * All cases require the packat to contain the full header. + */ + + ssize_t header_size_raw = packet_get_header_size(pkt); + if (header_size_raw < 0) { + // We don't have the header yet + goto do_ret; + } + size_t header_size = (size_t) header_size_raw; + + // Try Transfer-Encoding Chunked + char xfer_encode_str[] = "Transfer-Encoding: chunked"; + size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; + uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, + xfer_encode_str, + xfer_encode_str_size); + if (xfer_encode_pos != NULL) { + size = 0; + type = HTTP_CHUNKED; + goto do_ret; + } + + // Try Content-Length + char content_length_str[] = "Content-Length: "; + ssize_t contlen_size = + inspect_header_field(pkt, header_size, + content_length_str, sizeof(content_length_str) - 1); + if (contlen_size >= 0) { + size = (size_t) contlen_size + header_size; + type = HTTP_CONTENT_LENGTH; + goto do_ret; + } + + // GET requests (start with GET) or answers from the server (start + // with HTTP) + if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) || + doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) { + size = header_size; + type = HTTP_HEADER_ONLY; + goto do_ret; + } + + // No size was detectable yet header was found + type = HTTP_UNKNOWN; + size = 0; + + do_ret: + pkt->parent_message->claimed_size = size; + pkt->parent_message->type = type; + return type; } size_t packet_pending_bytes(struct http_packet_t *pkt) { - struct http_message_t *msg = pkt->parent_message; - - // Check Cache - if (pkt->expected_size > 0) - goto pending_known; - - if (HTTP_UNSET == msg->type) { - msg->type = packet_find_type(pkt); - - if (HTTP_CHUNKED == msg->type) { - // Note: this was the packet with the - // header of our chunked message. - - // Save any non-header data we got - ssize_t header_size = packet_get_header_size(pkt); - - // Sanity check - if (header_size < 0 || - (size_t)header_size > pkt->filled_size) - ERR_AND_EXIT("HTTP: Could not find header twice"); - - NOTE("HTTP: Chunked header size is %ld bytes", - header_size); - pkt->expected_size = (size_t) header_size; - msg->claimed_size = 0; - goto pending_known; - } - } - - - if (HTTP_CHUNKED == msg->type) { - if (pkt->filled_size == 0) { - // Grab chunk's mini-header - goto pending_known; - } - - if (pkt->expected_size == 0) { - // Check chunk's mini-header - ssize_t size = packet_find_chunked_size(pkt); - if (size <= 0) { - ERR("============================================="); - ERR("Malformed chunk-transport http header receivd"); - ERR("Missing chunk's mini-headers in first data"); - ERR("Have %d bytes", pkt->filled_size); - printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); - ERR("Malformed chunk-transport http header receivd"); - ERR("============================================="); - goto pending_known; - } - - pkt->expected_size = (size_t) size; - msg->claimed_size = 0; - } - - goto pending_known; - } - if (HTTP_HEADER_ONLY == msg->type) { - // Note: we can only know it is header only - // when the buffer already contains the header. - // So this next call cannot fail. - pkt->expected_size = (size_t) packet_get_header_size(pkt); - msg->claimed_size = pkt->expected_size; - goto pending_known; - } - if (HTTP_CONTENT_LENGTH == msg->type) { - // Note: find_header() has - // filled msg's claimed_size - msg->claimed_size = msg->claimed_size; - pkt->expected_size = msg->claimed_size; - goto pending_known; - } - -pending_known: - - // Save excess data - if (pkt->expected_size && pkt->filled_size > pkt->expected_size) - packet_store_excess(pkt); - - size_t expected = pkt->expected_size; - if (expected == 0) - expected = msg->claimed_size; - if (expected == 0) - expected = pkt->buffer_capacity; - - // Sanity check - if (expected < pkt->filled_size) - ERR_AND_EXIT("Expected cannot be larger than filled"); - - size_t pending = expected - pkt->filled_size; - - // Expand buffer as needed - while (pending + pkt->filled_size > pkt->buffer_capacity) { - ssize_t size_added = packet_expand(pkt); - if (size_added < 0) { - WARN("packet at max allowed size"); - return 0; - } - if (size_added == 0) { - ERR("Failed to expand packet"); - return 0; - } - } - - packet_check_completion(pkt); - - return pending; + struct http_message_t *msg = pkt->parent_message; + + // Check Cache + if (pkt->expected_size > 0) + goto pending_known; + + if (HTTP_UNSET == msg->type) { + msg->type = packet_find_type(pkt); + + if (HTTP_CHUNKED == msg->type) { + // Note: this was the packet with the + // header of our chunked message. + + // Save any non-header data we got + ssize_t header_size = packet_get_header_size(pkt); + + // Sanity check + if (header_size < 0 || + (size_t)header_size > pkt->filled_size) + ERR_AND_EXIT("HTTP: Could not find header twice"); + + NOTE("HTTP: Chunked header size is %ld bytes", + header_size); + pkt->expected_size = (size_t) header_size; + msg->claimed_size = 0; + goto pending_known; + } + } + + if (HTTP_CHUNKED == msg->type) { + if (pkt->filled_size == 0) { + // Grab chunk's mini-header + goto pending_known; + } + + if (pkt->expected_size == 0) { + // Check chunk's mini-header + ssize_t size = packet_find_chunked_size(pkt); + if (size <= 0) { + ERR("============================================="); + ERR("Malformed chunk-transport http header receivd"); + ERR("Missing chunk's mini-headers in first data"); + ERR("Have %d bytes", pkt->filled_size); + printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); + ERR("Malformed chunk-transport http header receivd"); + ERR("============================================="); + goto pending_known; + } + + pkt->expected_size = (size_t) size; + msg->claimed_size = 0; + } + + goto pending_known; + } + if (HTTP_HEADER_ONLY == msg->type) { + // Note: we can only know it is header only + // when the buffer already contains the header. + // So this next call cannot fail. + pkt->expected_size = (size_t) packet_get_header_size(pkt); + msg->claimed_size = pkt->expected_size; + goto pending_known; + } + if (HTTP_CONTENT_LENGTH == msg->type) { + // Note: find_header() has + // filled msg's claimed_size + msg->claimed_size = msg->claimed_size; + pkt->expected_size = msg->claimed_size; + goto pending_known; + } + + pending_known: + + // Save excess data + if (pkt->expected_size && pkt->filled_size > pkt->expected_size) + packet_store_excess(pkt); + + size_t expected = pkt->expected_size; + if (expected == 0) + expected = msg->claimed_size; + if (expected == 0) + expected = pkt->buffer_capacity; + + // Sanity check + if (expected < pkt->filled_size) + ERR_AND_EXIT("Expected cannot be larger than filled"); + + size_t pending = expected - pkt->filled_size; + + // Expand buffer as needed + while (pending + pkt->filled_size > pkt->buffer_capacity) { + ssize_t size_added = packet_expand(pkt); + if (size_added < 0) { + WARN("packet at max allowed size"); + return 0; + } + if (size_added == 0) { + ERR("Failed to expand packet"); + return 0; + } + } + + packet_check_completion(pkt); + + return pending; } void packet_mark_received(struct http_packet_t *pkt, size_t received) { - struct http_message_t *msg = pkt->parent_message; - msg->received_size += received; + struct http_message_t *msg = pkt->parent_message; + msg->received_size += received; - pkt->filled_size += received; - if (received) { - NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, " - "msg has %lu bytes", - received, pkt->filled_size, msg->received_size); - } + pkt->filled_size += received; + if (received) { + NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, " + "msg has %lu bytes", + received, pkt->filled_size, msg->received_size); + } - packet_check_completion(pkt); + packet_check_completion(pkt); - if (pkt->filled_size > pkt->buffer_capacity) - ERR_AND_EXIT("Overflowed packet's buffer"); + if (pkt->filled_size > pkt->buffer_capacity) + ERR_AND_EXIT("Overflowed packet's buffer"); - if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { - // Store excess data - packet_store_excess(pkt); - } + if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { + // Store excess data + packet_store_excess(pkt); + } } struct http_packet_t *packet_new(struct http_message_t *parent_msg) { - struct http_packet_t *pkt = NULL; - uint8_t *buf = NULL; - size_t const capacity = BUFFER_STEP; - - assert(parent_msg != NULL); - pkt = calloc(1, sizeof(*pkt)); - if (pkt == NULL) { - ERR("failed to alloc packet"); - return NULL; - } - pkt->parent_message = parent_msg; - pkt->expected_size = 0; - - // Claim any spare data from prior packets - packet_take_spare(pkt); - - if (pkt->buffer == NULL) { - buf = calloc(capacity, sizeof(*buf)); - if (buf == NULL) { - ERR("failed to alloc space for packet's buffer or space for packet"); - free(pkt); - return NULL; - } - - // Assemble packet - pkt->buffer = buf; - pkt->buffer_capacity = capacity; - pkt->filled_size = 0; - } - - return pkt; + struct http_packet_t *pkt = NULL; + uint8_t *buf = NULL; + size_t const capacity = BUFFER_STEP; + + assert(parent_msg != NULL); + pkt = calloc(1, sizeof(*pkt)); + if (pkt == NULL) { + ERR("failed to alloc packet"); + return NULL; + } + pkt->parent_message = parent_msg; + pkt->expected_size = 0; + + // Claim any spare data from prior packets + packet_take_spare(pkt); + + if (pkt->buffer == NULL) { + buf = calloc(capacity, sizeof(*buf)); + if (buf == NULL) { + ERR("failed to alloc space for packet's buffer or space for packet"); + free(pkt); + return NULL; + } + + // Assemble packet + pkt->buffer = buf; + pkt->buffer_capacity = capacity; + pkt->filled_size = 0; + } + + return pkt; } void packet_free(struct http_packet_t *pkt) { - free(pkt->buffer); - free(pkt); + free(pkt->buffer); + free(pkt); } #define MAX_PACKET_SIZE (1 << 26) // 64MiB ssize_t packet_expand(struct http_packet_t *pkt) { - size_t cur_size = pkt->buffer_capacity; - size_t new_size = cur_size * 2; - if (new_size > MAX_PACKET_SIZE) { - WARN("HTTP: cannot expand packet beyond limit"); - return -1; - } - NOTE("HTTP: doubling packet buffer to %lu", new_size); - - uint8_t *new_buf = realloc(pkt->buffer, new_size); - if (new_buf == NULL) { - // If realloc fails the original buffer is still valid - WARN("Failed to expand packet"); - return 0; - } - pkt->buffer = new_buf; - pkt->buffer_capacity = new_size; - - size_t diff = new_size - cur_size; - if (diff > SSIZE_MAX) - ERR_AND_EXIT("Buffer expanded beyond sane limit"); - return (ssize_t) diff; + size_t cur_size = pkt->buffer_capacity; + size_t new_size = cur_size * 2; + if (new_size > MAX_PACKET_SIZE) { + WARN("HTTP: cannot expand packet beyond limit"); + return -1; + } + NOTE("HTTP: doubling packet buffer to %lu", new_size); + + uint8_t *new_buf = realloc(pkt->buffer, new_size); + if (new_buf == NULL) { + // If realloc fails the original buffer is still valid + WARN("Failed to expand packet"); + return 0; + } + pkt->buffer = new_buf; + pkt->buffer_capacity = new_size; + + size_t diff = new_size - cur_size; + if (diff > SSIZE_MAX) + ERR_AND_EXIT("Buffer expanded beyond sane limit"); + return (ssize_t) diff; } @@ -17,41 +17,41 @@ #include <sys/types.h> enum http_request_t { - HTTP_UNSET, - HTTP_UNKNOWN, - HTTP_CHUNKED, - HTTP_CONTENT_LENGTH, - HTTP_HEADER_ONLY + HTTP_UNSET, + HTTP_UNKNOWN, + HTTP_CHUNKED, + HTTP_CONTENT_LENGTH, + HTTP_HEADER_ONLY }; struct http_message_t { - enum http_request_t type; + enum http_request_t type; - size_t spare_filled; - size_t spare_capacity; - uint8_t *spare_buffer; + size_t spare_filled; + size_t spare_capacity; + uint8_t *spare_buffer; - size_t unreceived_size; - uint8_t is_completed; + size_t unreceived_size; + uint8_t is_completed; - // Detected from child packets - size_t claimed_size; - size_t received_size; + // Detected from child packets + size_t claimed_size; + size_t received_size; }; struct http_packet_t { - // Cache - size_t header_size; + // Cache + size_t header_size; - size_t filled_size; - size_t expected_size; + size_t filled_size; + size_t expected_size; - size_t buffer_capacity; - uint8_t *buffer; + size_t buffer_capacity; + uint8_t *buffer; - struct http_message_t *parent_message; + struct http_message_t *parent_message; - uint8_t is_completed; + uint8_t is_completed; }; struct http_message_t *http_message_new(void); diff --git a/src/ippusbxd.c b/src/ippusbxd.c index d352593..3ac602f 100644 --- a/src/ippusbxd.c +++ b/src/ippusbxd.c @@ -31,515 +31,512 @@ #include "dnssd.h" struct service_thread_param { - struct tcp_conn_t *tcp; - struct usb_sock_t *usb_sock; - pthread_t thread_handle; - int thread_num; + struct tcp_conn_t *tcp; + struct usb_sock_t *usb_sock; + pthread_t thread_handle; + int thread_num; }; static void sigterm_handler(int sig) { - /* Flag that we should stop and return... */ - g_options.terminate = 1; - NOTE("Caught signal %d, shutting down ...", sig); + /* Flag that we should stop and return... */ + g_options.terminate = 1; + NOTE("Caught signal %d, shutting down ...", sig); } static void *service_connection(void *arg_void) { - struct service_thread_param *arg = - (struct service_thread_param *)arg_void; - - NOTE("Thread #%d: Starting", arg->thread_num); - // classify priority - struct usb_conn_t *usb = NULL; - int usb_failed = 0; - while (!arg->tcp->is_closed && usb_failed == 0 && !g_options.terminate) { - struct http_message_t *server_msg = NULL; - struct http_message_t *client_msg = NULL; - - // Client's request - client_msg = http_message_new(); - if (client_msg == NULL) { - ERR("Thread #%d: Failed to create client message", arg->thread_num); - break; - } - NOTE("Thread #%d: M %p: Client msg starting", - arg->thread_num, client_msg); - - while (!client_msg->is_completed) { - struct http_packet_t *pkt; - pkt = tcp_packet_get(arg->tcp, client_msg); - if (pkt == NULL) { - if (arg->tcp->is_closed) { - NOTE("Thread #%d: M %p: Client closed connection\n", - arg->thread_num, client_msg); - goto cleanup_subconn; - } - ERR("Thread #%d: M %p: Got null packet from tcp", - arg->thread_num, client_msg); - goto cleanup_subconn; - } - if (usb == NULL && arg->usb_sock != NULL) { - usb = usb_conn_acquire(arg->usb_sock); - if (usb == NULL) { - ERR("Thread #%d: M %p: Failed to acquire usb interface", - arg->thread_num, client_msg); - packet_free(pkt); - usb_failed = 1; - goto cleanup_subconn; - } - usb_failed = 0; - NOTE("Thread #%d: M %p: Interface #%d: acquired usb conn", - arg->thread_num, client_msg, - usb->interface_index); - } - - NOTE("Thread #%d: M %p P %p: Pkt from tcp (buffer size: %d)\n===\n%s===", - arg->thread_num, client_msg, pkt, - pkt->filled_size, - hexdump(pkt->buffer, (int)pkt->filled_size)); - // In no-printer mode we simply ignore passing the - // client message on to the printer - if (arg->usb_sock != NULL) { - if (usb_conn_packet_send(usb, pkt) != 0) { - ERR("Thread #%d: M %p P %p: Interface #%d: Unable to send client package via USB", - arg->thread_num, - client_msg, pkt, usb->interface_index); - packet_free(pkt); - goto cleanup_subconn; - } - NOTE("Thread #%d: M %p P %p: Interface #%d: Client pkt done", - arg->thread_num, - client_msg, pkt, usb->interface_index); - } - packet_free(pkt); - } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Client msg completed\n", - arg->thread_num, client_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Client msg completed\n", - arg->thread_num, client_msg); - message_free(client_msg); - client_msg = NULL; - - - // Server's response - server_msg = http_message_new(); - if (server_msg == NULL) { - ERR("Thread #%d: Failed to create server message", - arg->thread_num); - goto cleanup_subconn; - } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Server msg starting", - arg->thread_num, server_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Server msg starting", - arg->thread_num, server_msg); - while (!server_msg->is_completed) { - struct http_packet_t *pkt; - if (arg->usb_sock != NULL) { - pkt = usb_conn_packet_get(usb, server_msg); - if (pkt == NULL) { - usb_failed = 1; - goto cleanup_subconn; - } - } else { - // In no-printer mode we "invent" the answer - // of the printer, a simple HTML message as - // a pseudo web interface - pkt = packet_new(server_msg); - snprintf((char*)(pkt->buffer), - pkt->buffer_capacity - 1, - "HTTP/1.1 200 OK\r\nContent-Type: text/html; name=ippusbxd.html; charset=UTF-8\r\n\r\n<html><h2>ippusbxd</h2><p>Debug/development mode without connection to IPP-over-USB printer</p></html>\r\n"); - pkt->filled_size = 183; - // End the TCP connection, so that a - // web browser does not wait for more data - server_msg->is_completed = 1; - arg->tcp->is_closed = 1; - } - - NOTE("Thread #%d: M %p P %p: Pkt from usb (buffer size: %d)\n===\n%s===", - arg->thread_num, server_msg, pkt, pkt->filled_size, - hexdump(pkt->buffer, (int)pkt->filled_size)); - if (tcp_packet_send(arg->tcp, pkt) != 0) { - ERR("Thread #%d: M %p P %p: Unable to send client package via TCP", - arg->thread_num, - client_msg, pkt); - packet_free(pkt); - goto cleanup_subconn; - } - if (usb != NULL) - NOTE("Thread #%d: M %p P %p: Interface #%d: Server pkt done", - arg->thread_num, server_msg, pkt, - usb->interface_index); - else - NOTE("Thread #%d: M %p P %p: Server pkt done", - arg->thread_num, server_msg, pkt); - packet_free(pkt); - } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Server msg completed\n", - arg->thread_num, server_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Server msg completed\n", - arg->thread_num, server_msg); - -cleanup_subconn: - if (usb != NULL && (arg->tcp->is_closed || usb_failed == 1)) { - NOTE("Thread #%d: M %p: Interface #%d: releasing usb conn", - arg->thread_num, server_msg, usb->interface_index); - usb_conn_release(usb); - usb = NULL; - } - if (client_msg != NULL) - message_free(client_msg); - if (server_msg != NULL) - message_free(server_msg); + struct service_thread_param *arg = + (struct service_thread_param *)arg_void; + + NOTE("Thread #%d: Starting", arg->thread_num); + // classify priority + struct usb_conn_t *usb = NULL; + int usb_failed = 0; + while (!arg->tcp->is_closed && usb_failed == 0 && !g_options.terminate) { + struct http_message_t *server_msg = NULL; + struct http_message_t *client_msg = NULL; + + // Client's request + client_msg = http_message_new(); + if (client_msg == NULL) { + ERR("Thread #%d: Failed to create client message", arg->thread_num); + break; + } + NOTE("Thread #%d: M %p: Client msg starting", + arg->thread_num, client_msg); + + while (!client_msg->is_completed) { + struct http_packet_t *pkt; + pkt = tcp_packet_get(arg->tcp, client_msg); + if (pkt == NULL) { + if (arg->tcp->is_closed) { + NOTE("Thread #%d: M %p: Client closed connection\n", + arg->thread_num, client_msg); + goto cleanup_subconn; } - - - NOTE("Thread #%d: Closing", arg->thread_num); - tcp_conn_close(arg->tcp); - free(arg); - return NULL; -} - -static void start_daemon() -{ - // Capture USB device if not in no-printer mode - struct usb_sock_t *usb_sock; - - // Termination flag - g_options.terminate = 0; - - if (g_options.noprinter_mode == 0) { - usb_sock = usb_open(); - if (usb_sock == NULL) - goto cleanup_usb; - } else - usb_sock = NULL; - - // Capture a socket - uint16_t desired_port = g_options.desired_port; - struct tcp_sock_t *tcp_socket = NULL, *tcp6_socket = NULL; - for (;;) { - tcp_socket = tcp_open(desired_port, g_options.interface); - tcp6_socket = tcp6_open(desired_port, g_options.interface); - if (tcp_socket || tcp6_socket || g_options.only_desired_port) - break; - // Search for a free port - desired_port ++; - // We failed with 0 as port number or we reached the max - // port number - if (desired_port == 1 || desired_port == 0) - // IANA recommendation of 49152 to 65535 for ephemeral - // ports - // https://en.wikipedia.org/wiki/Ephemeral_port - desired_port = 49152; - NOTE("Access to desired port failed, trying alternative port %d", desired_port); + ERR("Thread #%d: M %p: Got null packet from tcp", + arg->thread_num, client_msg); + goto cleanup_subconn; + } + if (usb == NULL && arg->usb_sock != NULL) { + usb = usb_conn_acquire(arg->usb_sock); + if (usb == NULL) { + ERR("Thread #%d: M %p: Failed to acquire usb interface", + arg->thread_num, client_msg); + packet_free(pkt); + usb_failed = 1; + goto cleanup_subconn; } - if (tcp_socket == NULL && tcp6_socket == NULL) - goto cleanup_tcp; - - if (tcp_socket) - g_options.real_port = tcp_port_number_get(tcp_socket); - else - g_options.real_port = tcp_port_number_get(tcp6_socket); - if (desired_port != 0 && g_options.only_desired_port == 1 && - desired_port != g_options.real_port) { - ERR("Received port number did not match requested port number." - " The requested port number may be too high."); - goto cleanup_tcp; + usb_failed = 0; + NOTE("Thread #%d: M %p: Interface #%d: acquired usb conn", + arg->thread_num, client_msg, + usb->interface_index); + } + + NOTE("Thread #%d: M %p P %p: Pkt from tcp (buffer size: %d)\n===\n%s===", + arg->thread_num, client_msg, pkt, + pkt->filled_size, + hexdump(pkt->buffer, (int)pkt->filled_size)); + // In no-printer mode we simply ignore passing the + // client message on to the printer + if (arg->usb_sock != NULL) { + if (usb_conn_packet_send(usb, pkt) != 0) { + ERR("Thread #%d: M %p P %p: Interface #%d: Unable to send client package via USB", + arg->thread_num, + client_msg, pkt, usb->interface_index); + packet_free(pkt); + goto cleanup_subconn; } - printf("%u|", g_options.real_port); - fflush(stdout); - - NOTE("Port: %d, IPv4 %savailable, IPv6 %savailable", - g_options.real_port, tcp_socket ? "" : "not ", tcp6_socket ? "" : "not "); - - // Lose connection to caller - uint16_t pid; - if (!g_options.nofork_mode && (pid = fork()) > 0) { - printf("%u|", pid); - exit(0); + NOTE("Thread #%d: M %p P %p: Interface #%d: Client pkt done", + arg->thread_num, + client_msg, pkt, usb->interface_index); + } + packet_free(pkt); + } + if (usb != NULL) + NOTE("Thread #%d: M %p: Interface #%d: Client msg completed\n", + arg->thread_num, client_msg, + usb->interface_index); + else + NOTE("Thread #%d: M %p: Client msg completed\n", + arg->thread_num, client_msg); + message_free(client_msg); + client_msg = NULL; + + // Server's response + server_msg = http_message_new(); + if (server_msg == NULL) { + ERR("Thread #%d: Failed to create server message", + arg->thread_num); + goto cleanup_subconn; + } + if (usb != NULL) + NOTE("Thread #%d: M %p: Interface #%d: Server msg starting", + arg->thread_num, server_msg, + usb->interface_index); + else + NOTE("Thread #%d: M %p: Server msg starting", + arg->thread_num, server_msg); + while (!server_msg->is_completed) { + struct http_packet_t *pkt; + if (arg->usb_sock != NULL) { + pkt = usb_conn_packet_get(usb, server_msg); + if (pkt == NULL) { + usb_failed = 1; + goto cleanup_subconn; } + } else { + // In no-printer mode we "invent" the answer + // of the printer, a simple HTML message as + // a pseudo web interface + pkt = packet_new(server_msg); + snprintf((char*)(pkt->buffer), + pkt->buffer_capacity - 1, + "HTTP/1.1 200 OK\r\nContent-Type: text/html; name=ippusbxd.html; charset=UTF-8\r\n\r\n<html><h2>ippusbxd</h2><p>Debug/development mode without connection to IPP-over-USB printer</p></html>\r\n"); + pkt->filled_size = 183; + // End the TCP connection, so that a + // web browser does not wait for more data + server_msg->is_completed = 1; + arg->tcp->is_closed = 1; + } + + NOTE("Thread #%d: M %p P %p: Pkt from usb (buffer size: %d)\n===\n%s===", + arg->thread_num, server_msg, pkt, pkt->filled_size, + hexdump(pkt->buffer, (int)pkt->filled_size)); + if (tcp_packet_send(arg->tcp, pkt) != 0) { + ERR("Thread #%d: M %p P %p: Unable to send client package via TCP", + arg->thread_num, + client_msg, pkt); + packet_free(pkt); + goto cleanup_subconn; + } + if (usb != NULL) + NOTE("Thread #%d: M %p P %p: Interface #%d: Server pkt done", + arg->thread_num, server_msg, pkt, + usb->interface_index); + else + NOTE("Thread #%d: M %p P %p: Server pkt done", + arg->thread_num, server_msg, pkt); + packet_free(pkt); + } + if (usb != NULL) + NOTE("Thread #%d: M %p: Interface #%d: Server msg completed\n", + arg->thread_num, server_msg, + usb->interface_index); + else + NOTE("Thread #%d: M %p: Server msg completed\n", + arg->thread_num, server_msg); + + cleanup_subconn: + if (usb != NULL && (arg->tcp->is_closed || usb_failed == 1)) { + NOTE("Thread #%d: M %p: Interface #%d: releasing usb conn", + arg->thread_num, server_msg, usb->interface_index); + usb_conn_release(usb); + usb = NULL; + } + if (client_msg != NULL) + message_free(client_msg); + if (server_msg != NULL) + message_free(server_msg); + } + + NOTE("Thread #%d: Closing", arg->thread_num); + tcp_conn_close(arg->tcp); + free(arg); + return NULL; +} - // Redirect SIGINT and SIGTERM so that we do a proper shutdown, unregistering - // the printer from DNS-SD +static void start_daemon() +{ + // Capture USB device if not in no-printer mode + struct usb_sock_t *usb_sock; + + // Termination flag + g_options.terminate = 0; + + if (g_options.noprinter_mode == 0) { + usb_sock = usb_open(); + if (usb_sock == NULL) + goto cleanup_usb; + } else + usb_sock = NULL; + + // Capture a socket + uint16_t desired_port = g_options.desired_port; + struct tcp_sock_t *tcp_socket = NULL, *tcp6_socket = NULL; + for (;;) { + tcp_socket = tcp_open(desired_port, g_options.interface); + tcp6_socket = tcp6_open(desired_port, g_options.interface); + if (tcp_socket || tcp6_socket || g_options.only_desired_port) + break; + // Search for a free port + desired_port ++; + // We failed with 0 as port number or we reached the max + // port number + if (desired_port == 1 || desired_port == 0) + // IANA recommendation of 49152 to 65535 for ephemeral + // ports + // https://en.wikipedia.org/wiki/Ephemeral_port + desired_port = 49152; + NOTE("Access to desired port failed, trying alternative port %d", desired_port); + } + if (tcp_socket == NULL && tcp6_socket == NULL) + goto cleanup_tcp; + + if (tcp_socket) + g_options.real_port = tcp_port_number_get(tcp_socket); + else + g_options.real_port = tcp_port_number_get(tcp6_socket); + if (desired_port != 0 && g_options.only_desired_port == 1 && + desired_port != g_options.real_port) { + ERR("Received port number did not match requested port number." + " The requested port number may be too high."); + goto cleanup_tcp; + } + printf("%u|", g_options.real_port); + fflush(stdout); + + NOTE("Port: %d, IPv4 %savailable, IPv6 %savailable", + g_options.real_port, tcp_socket ? "" : "not ", tcp6_socket ? "" : "not "); + + // Lose connection to caller + uint16_t pid; + if (!g_options.nofork_mode && (pid = fork()) > 0) { + printf("%u|", pid); + exit(0); + } + + // Redirect SIGINT and SIGTERM so that we do a proper shutdown, unregistering + // the printer from DNS-SD #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ - sigset(SIGTERM, sigterm_handler); - sigset(SIGINT, sigterm_handler); - NOTE("Using signal handler SIGSET"); + sigset(SIGTERM, sigterm_handler); + sigset(SIGINT, sigterm_handler); + NOTE("Using signal handler SIGSET"); #elif defined(HAVE_SIGACTION) - struct sigaction action; /* Actions for POSIX signals */ - memset(&action, 0, sizeof(action)); - sigemptyset(&action.sa_mask); - sigaddset(&action.sa_mask, SIGTERM); - action.sa_handler = sigterm_handler; - sigaction(SIGTERM, &action, NULL); - sigemptyset(&action.sa_mask); - sigaddset(&action.sa_mask, SIGINT); - action.sa_handler = sigterm_handler; - sigaction(SIGINT, &action, NULL); - NOTE("Using signal handler SIGACTION"); + struct sigaction action; /* Actions for POSIX signals */ + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGTERM); + action.sa_handler = sigterm_handler; + sigaction(SIGTERM, &action, NULL); + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_handler = sigterm_handler; + sigaction(SIGINT, &action, NULL); + NOTE("Using signal handler SIGACTION"); #else - signal(SIGTERM, sigterm_handler); - signal(SIGINT, sigterm_handler); - NOTE("Using signal handler SIGNAL"); + signal(SIGTERM, sigterm_handler); + signal(SIGINT, sigterm_handler); + NOTE("Using signal handler SIGNAL"); #endif /* HAVE_SIGSET */ - // Register for unplug event - if (usb_can_callback(usb_sock)) - usb_register_callback(usb_sock); - - // DNS-SD-broadcast the printer on the local machine so - // that cups-browsed and ippfind will discover it - if (usb_sock && g_options.nobroadcast == 0) { - if (dnssd_init() == -1) - goto cleanup_tcp; - } - - // Main loop - int i = 0; - while (!g_options.terminate) { - i ++; - struct service_thread_param *args = calloc(1, sizeof(*args)); - if (args == NULL) { - ERR("Preparing thread #%d: Failed to alloc space for thread args", - i); - goto cleanup_thread; - } - - args->thread_num = i; - args->usb_sock = usb_sock; - - // For each request/response round we use the socket (IPv4 or - // IPv6) which receives data first - args->tcp = tcp_conn_select(tcp_socket, tcp6_socket); - if (g_options.terminate) - goto cleanup_thread; - if (args->tcp == NULL) { - ERR("Preparing thread #%d: Failed to open tcp connection", i); - goto cleanup_thread; - } - - int status = pthread_create(&args->thread_handle, NULL, - &service_connection, args); - if (status) { - ERR("Creating thread #%d: Failed to spawn thread, error %d", - i, status); - goto cleanup_thread; - } - - continue; - - cleanup_thread: - if (args != NULL) { - if (args->tcp != NULL) - tcp_conn_close(args->tcp); - free(args); - } - break; - } - -cleanup_tcp: - if (g_options.dnssd_data != NULL) - dnssd_shutdown(); - - if (tcp_socket!= NULL) - tcp_close(tcp_socket); - if (tcp6_socket!= NULL) - tcp_close(tcp6_socket); -cleanup_usb: - if (usb_sock != NULL) - usb_close(usb_sock); - return; + // Register for unplug event + if (usb_can_callback(usb_sock)) + usb_register_callback(usb_sock); + + // DNS-SD-broadcast the printer on the local machine so + // that cups-browsed and ippfind will discover it + if (usb_sock && g_options.nobroadcast == 0) { + if (dnssd_init() == -1) + goto cleanup_tcp; + } + + // Main loop + int i = 0; + while (!g_options.terminate) { + i ++; + struct service_thread_param *args = calloc(1, sizeof(*args)); + if (args == NULL) { + ERR("Preparing thread #%d: Failed to alloc space for thread args", + i); + goto cleanup_thread; + } + + args->thread_num = i; + args->usb_sock = usb_sock; + + // For each request/response round we use the socket (IPv4 or + // IPv6) which receives data first + args->tcp = tcp_conn_select(tcp_socket, tcp6_socket); + if (g_options.terminate) + goto cleanup_thread; + if (args->tcp == NULL) { + ERR("Preparing thread #%d: Failed to open tcp connection", i); + goto cleanup_thread; + } + + int status = pthread_create(&args->thread_handle, NULL, + &service_connection, args); + if (status) { + ERR("Creating thread #%d: Failed to spawn thread, error %d", + i, status); + goto cleanup_thread; + } + + continue; + + cleanup_thread: + if (args != NULL) { + if (args->tcp != NULL) + tcp_conn_close(args->tcp); + free(args); + } + break; + } + + cleanup_tcp: + if (g_options.dnssd_data != NULL) + dnssd_shutdown(); + + if (tcp_socket!= NULL) + tcp_close(tcp_socket); + if (tcp6_socket!= NULL) + tcp_close(tcp6_socket); + cleanup_usb: + if (usb_sock != NULL) + usb_close(usb_sock); + return; } static uint16_t strto16hex(const char *str) { - unsigned long val = strtoul(str, NULL, 16); - if (val > UINT16_MAX) - exit(1); - return (uint16_t)val; + unsigned long val = strtoul(str, NULL, 16); + if (val > UINT16_MAX) + exit(1); + return (uint16_t)val; } static uint16_t strto16dec(const char *str) { - unsigned long val = strtoul(str, NULL, 10); - if (val > UINT16_MAX) - exit(1); - return (uint16_t)val; + unsigned long val = strtoul(str, NULL, 10); + if (val > UINT16_MAX) + exit(1); + return (uint16_t)val; } int main(int argc, char *argv[]) { - int c; - int option_index = 0; - static struct option long_options[] = { - {"vid", required_argument, 0, 'v' }, - {"pid", required_argument, 0, 'm' }, - {"serial", required_argument, 0, 's' }, - {"bus", required_argument, 0, 'b' }, - {"device", required_argument, 0, 'D' }, - {"bus-device", required_argument, 0, 'X' }, - {"from-port", required_argument, 0, 'P' }, - {"only-port", required_argument, 0, 'p' }, - {"interface", required_argument, 0, 'i' }, - {"logging", no_argument, 0, 'l' }, - {"debug", no_argument, 0, 'd' }, - {"verbose", no_argument, 0, 'q' }, - {"no-fork", no_argument, 0, 'n' }, - {"no-broadcast", no_argument, 0, 'B' }, - {"no-printer", no_argument, 0, 'N' }, - {"help", no_argument, 0, 'h' }, - {NULL, 0, 0, 0 } - }; - g_options.log_destination = LOGGING_STDERR; - g_options.only_desired_port = 1; - g_options.interface = "lo"; - g_options.serial_num = NULL; - g_options.vendor_id = 0; - g_options.product_id = 0; - g_options.bus = 0; - g_options.device = 0; - - while ((c = getopt_long(argc, argv, "qnhdp:P:i:s:lv:m:NB", - long_options, &option_index)) != -1) { - switch (c) { - case '?': - case 'h': - g_options.help_mode = 1; - break; - case 'p': - case 'P': - { - long long port = 0; - // Request specific port - port = atoi(optarg); - if (port < 0) { - ERR("Port number must be non-negative"); - return 1; - } - if (port > UINT16_MAX) { - ERR("Port number must be %u or less, " - "but not negative", UINT16_MAX); - return 2; - } - g_options.desired_port = (uint16_t)port; - if (c == 'p') - g_options.only_desired_port = 1; - else - g_options.only_desired_port = 0; - break; - } - case 'i': - // Request a specific network interface - g_options.interface = strdup(optarg); - break; - case 'l': - g_options.log_destination = LOGGING_SYSLOG; - break; - case 'd': - g_options.nofork_mode = 1; - g_options.verbose_mode = 1; - break; - case 'q': - g_options.verbose_mode = 1; - break; - case 'n': - g_options.nofork_mode = 1; - break; - case 'v': - g_options.vendor_id = strto16hex(optarg); - break; - case 'm': - g_options.product_id = strto16hex(optarg); - break; - case 'b': - g_options.bus = strto16dec(optarg); - break; - case 'D': - g_options.device = strto16dec(optarg); - break; - case 'X': - { - char *p = strchr(optarg, ':'); - if (p == NULL) { - ERR("Bus and device must be given in the format <bus>:<device>"); - return 3; - } - p ++; - g_options.bus = strto16dec(optarg); - g_options.device = strto16dec(p); - break; - } - case 's': - g_options.serial_num = (unsigned char *)optarg; - break; - case 'N': - g_options.noprinter_mode = 1; - break; - case 'B': - g_options.nobroadcast = 1; - break; - } + int c; + int option_index = 0; + static struct option long_options[] = { + {"vid", required_argument, 0, 'v' }, + {"pid", required_argument, 0, 'm' }, + {"serial", required_argument, 0, 's' }, + {"bus", required_argument, 0, 'b' }, + {"device", required_argument, 0, 'D' }, + {"bus-device", required_argument, 0, 'X' }, + {"from-port", required_argument, 0, 'P' }, + {"only-port", required_argument, 0, 'p' }, + {"interface", required_argument, 0, 'i' }, + {"logging", no_argument, 0, 'l' }, + {"debug", no_argument, 0, 'd' }, + {"verbose", no_argument, 0, 'q' }, + {"no-fork", no_argument, 0, 'n' }, + {"no-broadcast", no_argument, 0, 'B' }, + {"no-printer", no_argument, 0, 'N' }, + {"help", no_argument, 0, 'h' }, + {NULL, 0, 0, 0 } + }; + g_options.log_destination = LOGGING_STDERR; + g_options.only_desired_port = 1; + g_options.interface = "lo"; + g_options.serial_num = NULL; + g_options.vendor_id = 0; + g_options.product_id = 0; + g_options.bus = 0; + g_options.device = 0; + + while ((c = getopt_long(argc, argv, "qnhdp:P:i:s:lv:m:NB", + long_options, &option_index)) != -1) { + switch (c) { + case '?': + case 'h': + g_options.help_mode = 1; + break; + case 'p': + case 'P': + { + long long port = 0; + // Request specific port + port = atoi(optarg); + if (port < 0) { + ERR("Port number must be non-negative"); + return 1; } - - if (g_options.help_mode) { - printf( - "Usage: %s -v <vendorid> -m <productid> -s <serial> -P <port>\n" - " %s --bus <bus> --device <device> -P <port>\n" - " %s -h\n" - "Options:\n" - " --help\n" - " -h Show this help message\n" - " --vid <vid>\n" - " -v <vid> Vendor ID of desired printer (as hexadecimal number)\n" - " --pid <pid>\n" - " -m <pid> Product ID of desired printer (as hexadecimal number)\n" - " --serial <serial>\n" - " -s <serial> Serial number of desired printer\n" - " --bus <bus>\n" - " --device <device>\n" - " --bus-device <bus>:<device>\n" - " USB bus and device numbers where the device is currently\n" - " connected (see output of lsusb). Note that these numbers change\n" - " when the device is disconnected and reconnected. This method of\n" - " calling ippusbxd is only for calling via UDEV. <bus> and\n" - " <device> have to be given in decimal numbers.\n" - " --only-port <portnum>\n" - " -p <portnum> Port number to bind against, error out if port already taken\n" - " --from-port <portnum>\n" - " -P <portnum> Port number to bind against, use another port if port already\n" - " taken\n" - " --interface <interface>\n" - " -i <interface> Network interface to use. Default is the loopback interface\n" - " (lo, localhost). As the loopback interface does not allow\n" - " DNS-SD broadcasting with Avahi, set up the dummy interface\n" - " (dummy0) for DNS-SD broadcasting.\n" - " --logging\n" - " -l Redirect logging to syslog\n" - " --verbose\n" - " -q Enable verbose tracing\n" - " --debug\n" - " -d Debug mode for verbose output and no fork\n" - " --no-fork\n" - " -n No-fork mode\n" - " --no-broadcast\n" - " -B No-broadcast mode, do not DNS-SD-broadcast\n" - " --no-printer\n" - " -N No-printer mode, debug/developer mode which makes ippusbxd\n" - " run without IPP-over-USB printer\n" - , argv[0], argv[0], argv[0]); - return 0; + if (port > UINT16_MAX) { + ERR("Port number must be %u or less, " + "but not negative", UINT16_MAX); + return 2; } - - start_daemon(); - return 0; + g_options.desired_port = (uint16_t)port; + if (c == 'p') + g_options.only_desired_port = 1; + else + g_options.only_desired_port = 0; + break; + } + case 'i': + // Request a specific network interface + g_options.interface = strdup(optarg); + break; + case 'l': + g_options.log_destination = LOGGING_SYSLOG; + break; + case 'd': + g_options.nofork_mode = 1; + g_options.verbose_mode = 1; + break; + case 'q': + g_options.verbose_mode = 1; + break; + case 'n': + g_options.nofork_mode = 1; + break; + case 'v': + g_options.vendor_id = strto16hex(optarg); + break; + case 'm': + g_options.product_id = strto16hex(optarg); + break; + case 'b': + g_options.bus = strto16dec(optarg); + break; + case 'D': + g_options.device = strto16dec(optarg); + break; + case 'X': + { + char *p = strchr(optarg, ':'); + if (p == NULL) { + ERR("Bus and device must be given in the format <bus>:<device>"); + return 3; + } + p ++; + g_options.bus = strto16dec(optarg); + g_options.device = strto16dec(p); + break; + } + case 's': + g_options.serial_num = (unsigned char *)optarg; + break; + case 'N': + g_options.noprinter_mode = 1; + break; + case 'B': + g_options.nobroadcast = 1; + break; + } + } + + if (g_options.help_mode) { + printf("Usage: %s -v <vendorid> -m <productid> -s <serial> -P <port>\n" + " %s --bus <bus> --device <device> -P <port>\n" + " %s -h\n" + "Options:\n" + " --help\n" + " -h Show this help message\n" + " --vid <vid>\n" + " -v <vid> Vendor ID of desired printer (as hexadecimal number)\n" + " --pid <pid>\n" + " -m <pid> Product ID of desired printer (as hexadecimal number)\n" + " --serial <serial>\n" + " -s <serial> Serial number of desired printer\n" + " --bus <bus>\n" + " --device <device>\n" + " --bus-device <bus>:<device>\n" + " USB bus and device numbers where the device is currently\n" + " connected (see output of lsusb). Note that these numbers change\n" + " when the device is disconnected and reconnected. This method of\n" + " calling ippusbxd is only for calling via UDEV. <bus> and\n" + " <device> have to be given in decimal numbers.\n" + " --only-port <portnum>\n" + " -p <portnum> Port number to bind against, error out if port already taken\n" + " --from-port <portnum>\n" + " -P <portnum> Port number to bind against, use another port if port already\n" + " taken\n" + " --interface <interface>\n" + " -i <interface> Network interface to use. Default is the loopback interface\n" + " (lo, localhost). As the loopback interface does not allow\n" + " DNS-SD broadcasting with Avahi, set up the dummy interface\n" + " (dummy0) for DNS-SD broadcasting.\n" + " --logging\n" + " -l Redirect logging to syslog\n" + " --verbose\n" + " -q Enable verbose tracing\n" + " --debug\n" + " -d Debug mode for verbose output and no fork\n" + " --no-fork\n" + " -n No-fork mode\n" + " --no-broadcast\n" + " -B No-broadcast mode, do not DNS-SD-broadcast\n" + " --no-printer\n" + " -N No-printer mode, debug/developer mode which makes ippusbxd\n" + " run without IPP-over-USB printer\n" + , argv[0], argv[0], argv[0]); + return 0; + } + + start_daemon(); + return 0; } diff --git a/src/logging.c b/src/logging.c index 708f7a1..eba1e15 100644 --- a/src/logging.c +++ b/src/logging.c @@ -24,16 +24,16 @@ void BASE_LOG(enum log_level level, const char *fmt, ...) { - if (!g_options.verbose_mode && level != LOGGING_ERROR) - return; + if (!g_options.verbose_mode && level != LOGGING_ERROR) + return; - va_list arg; - va_start(arg, fmt); - if (g_options.log_destination == LOGGING_STDERR) - vfprintf(stderr, fmt, arg); - else if (g_options.log_destination == LOGGING_SYSLOG) - syslog(LOG_ERR, fmt, arg); - va_end(arg); + va_list arg; + va_start(arg, fmt); + if (g_options.log_destination == LOGGING_STDERR) + vfprintf(stderr, fmt, arg); + else if (g_options.log_destination == LOGGING_SYSLOG) + syslog(LOG_ERR, fmt, arg); + va_end(arg); } char* hexdump (void *addr, int len) { diff --git a/src/logging.h b/src/logging.h index 704108d..45b00d8 100644 --- a/src/logging.h +++ b/src/logging.h @@ -19,10 +19,10 @@ #define TID() (pthread_self()) enum log_level { - LOGGING_ERROR, - LOGGING_WARNING, - LOGGING_NOTICE, - LOGGING_CONFORMANCE, + LOGGING_ERROR, + LOGGING_WARNING, + LOGGING_NOTICE, + LOGGING_CONFORMANCE, }; #define PP_CAT(x, y) PP_CAT_2(x, y) diff --git a/src/options.h b/src/options.h index 3e1af6c..02f3c04 100644 --- a/src/options.h +++ b/src/options.h @@ -17,36 +17,36 @@ #include "dnssd.h" enum log_target { - LOGGING_STDERR, - LOGGING_SYSLOG + LOGGING_STDERR, + LOGGING_SYSLOG }; struct options { - // Runtime configuration - uint16_t desired_port; - int only_desired_port; - uint16_t real_port; - char *interface; - enum log_target log_destination; + // Runtime configuration + uint16_t desired_port; + int only_desired_port; + uint16_t real_port; + char *interface; + enum log_target log_destination; - // Behavior - int help_mode; - int verbose_mode; - int nofork_mode; - int noprinter_mode; - int nobroadcast; + // Behavior + int help_mode; + int verbose_mode; + int nofork_mode; + int noprinter_mode; + int nobroadcast; - // Printer identity - unsigned char *serial_num; - int vendor_id; - int product_id; - int bus; - int device; - char *device_id; + // Printer identity + unsigned char *serial_num; + int vendor_id; + int product_id; + int bus; + int device; + char *device_id; - // Global variables - int terminate; - dnssd_t *dnssd_data; + // Global variables + int terminate; + dnssd_t *dnssd_data; }; extern struct options g_options; @@ -37,317 +37,315 @@ struct tcp_sock_t *tcp_open(uint16_t port, char* interface) { - struct tcp_sock_t *this = calloc(1, sizeof *this); - if (this == NULL) { - ERR("IPv4: callocing this failed"); - goto error; - } - - // Open [S]ocket [D]escriptor - this->sd = -1; - this->sd = socket(AF_INET, SOCK_STREAM, 0); - if (this->sd < 0) { - ERR("IPv4 socket open failed"); - goto error; - } - - // Find the IP address for the selected interface - struct ifaddrs *ifaddr, *ifa; - getifaddrs(&ifaddr); - for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - if ((strcmp(ifa->ifa_name, interface) == 0) && - (ifa->ifa_addr->sa_family == AF_INET)) - break; - } - if (ifa == NULL) { - ERR("Interface %s does not exist or IPv4 IP not found.", interface); - goto error; - } - - // Configure socket params - struct sockaddr_in addr, *if_addr; - if_addr = (struct sockaddr_in *) ifa->ifa_addr; - memset(&addr, 0, sizeof addr); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = if_addr->sin_addr.s_addr; - //addr.sin_addr.s_addr = htonl(0xC0A8000F); - NOTE("IPv4: Binding to %s:%d", inet_ntoa(if_addr->sin_addr), port); - - // Bind to the interface/IP/port - if (bind(this->sd, - (struct sockaddr *)&addr, - sizeof addr) < 0) { - if (g_options.only_desired_port == 1) - ERR("IPv4 bind on port failed. " - "Requested port may be taken or require root permissions."); - goto error; - } - - // Let kernel over-accept max number of connections - if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { - ERR("IPv4 listen failed on socket"); - goto error; - } - - return this; - -error: - if (this != NULL) { - if (this->sd != -1) { - close(this->sd); - } - free(this); - } - return NULL; + struct tcp_sock_t *this = calloc(1, sizeof *this); + if (this == NULL) { + ERR("IPv4: callocing this failed"); + goto error; + } + + // Open [S]ocket [D]escriptor + this->sd = -1; + this->sd = socket(AF_INET, SOCK_STREAM, 0); + if (this->sd < 0) { + ERR("IPv4 socket open failed"); + goto error; + } + + // Find the IP address for the selected interface + struct ifaddrs *ifaddr, *ifa; + getifaddrs(&ifaddr); + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if ((strcmp(ifa->ifa_name, interface) == 0) && + (ifa->ifa_addr->sa_family == AF_INET)) + break; + } + if (ifa == NULL) { + ERR("Interface %s does not exist or IPv4 IP not found.", interface); + goto error; + } + + // Configure socket params + struct sockaddr_in addr, *if_addr; + if_addr = (struct sockaddr_in *) ifa->ifa_addr; + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = if_addr->sin_addr.s_addr; + //addr.sin_addr.s_addr = htonl(0xC0A8000F); + NOTE("IPv4: Binding to %s:%d", inet_ntoa(if_addr->sin_addr), port); + + // Bind to the interface/IP/port + if (bind(this->sd, + (struct sockaddr *)&addr, + sizeof addr) < 0) { + if (g_options.only_desired_port == 1) + ERR("IPv4 bind on port failed. " + "Requested port may be taken or require root permissions."); + goto error; + } + + // Let kernel over-accept max number of connections + if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { + ERR("IPv4 listen failed on socket"); + goto error; + } + + return this; + + error: + if (this != NULL) { + if (this->sd != -1) { + close(this->sd); + } + free(this); + } + return NULL; } struct tcp_sock_t *tcp6_open(uint16_t port, char* interface) { - struct tcp_sock_t *this = calloc(1, sizeof *this); - if (this == NULL) { - ERR("IPv6: callocing this failed"); - goto error; - } - - // Open [S]ocket [D]escriptor - this->sd = -1; - this->sd = socket(AF_INET6, SOCK_STREAM, 0); - if (this->sd < 0) { - ERR("Ipv6 socket open failed"); - goto error; - } - - // Find the IP address for the selected interface - struct ifaddrs *ifaddr, *ifa; - getifaddrs(&ifaddr); - for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - if ((strcmp(ifa->ifa_name, interface) == 0) && - (ifa->ifa_addr->sa_family == AF_INET6)) - break; - } - if (ifa == NULL) { - ERR("Interface %s does not exist or IPv6 IP not found.", interface); - goto error; - } - - // Configure socket params - struct sockaddr_in6 addr, *if_addr; - char buf[64]; - if_addr = (struct sockaddr_in6 *) ifa->ifa_addr; - memset(&addr, 0, sizeof addr); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(port); - addr.sin6_addr = if_addr->sin6_addr; - addr.sin6_scope_id=if_nametoindex(interface); - if (inet_ntop(addr.sin6_family, (void *)&(addr.sin6_addr), - buf, sizeof(buf)) == NULL) { - ERR("Could not determine IPv6 IP address for interface %s.", - interface); - goto error; - } - NOTE("IPv6: Binding to [%s]:%d", buf, port); - - // Bind to the interface/IP/port - if (bind(this->sd, - (struct sockaddr *)&addr, - sizeof addr) < 0) { - if (g_options.only_desired_port == 1) - ERR("IPv6 bind on port failed. " - "Requested port may be taken or require root permissions."); - goto error; - } - - // Let kernel over-accept max number of connections - if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { - ERR("IPv6 listen failed on socket"); - goto error; - } - - return this; - -error: - if (this != NULL) { - if (this->sd != -1) { - close(this->sd); - } - free(this); - } - return NULL; + struct tcp_sock_t *this = calloc(1, sizeof *this); + if (this == NULL) { + ERR("IPv6: callocing this failed"); + goto error; + } + + // Open [S]ocket [D]escriptor + this->sd = -1; + this->sd = socket(AF_INET6, SOCK_STREAM, 0); + if (this->sd < 0) { + ERR("Ipv6 socket open failed"); + goto error; + } + + // Find the IP address for the selected interface + struct ifaddrs *ifaddr, *ifa; + getifaddrs(&ifaddr); + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if ((strcmp(ifa->ifa_name, interface) == 0) && + (ifa->ifa_addr->sa_family == AF_INET6)) + break; + } + if (ifa == NULL) { + ERR("Interface %s does not exist or IPv6 IP not found.", interface); + goto error; + } + + // Configure socket params + struct sockaddr_in6 addr, *if_addr; + char buf[64]; + if_addr = (struct sockaddr_in6 *) ifa->ifa_addr; + memset(&addr, 0, sizeof addr); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + addr.sin6_addr = if_addr->sin6_addr; + addr.sin6_scope_id=if_nametoindex(interface); + if (inet_ntop(addr.sin6_family, (void *)&(addr.sin6_addr), + buf, sizeof(buf)) == NULL) { + ERR("Could not determine IPv6 IP address for interface %s.", + interface); + goto error; + } + NOTE("IPv6: Binding to [%s]:%d", buf, port); + + // Bind to the interface/IP/port + if (bind(this->sd, + (struct sockaddr *)&addr, + sizeof addr) < 0) { + if (g_options.only_desired_port == 1) + ERR("IPv6 bind on port failed. " + "Requested port may be taken or require root permissions."); + goto error; + } + + // Let kernel over-accept max number of connections + if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { + ERR("IPv6 listen failed on socket"); + goto error; + } + + return this; + + error: + if (this != NULL) { + if (this->sd != -1) { + close(this->sd); + } + free(this); + } + return NULL; } void tcp_close(struct tcp_sock_t *this) { - close(this->sd); - free(this); + close(this->sd); + free(this); } uint16_t tcp_port_number_get(struct tcp_sock_t *sock) { - sock->info_size = sizeof sock->info; - int query_status = getsockname( - sock->sd, - (struct sockaddr *) &(sock->info), - &(sock->info_size)); - if (query_status == -1) { - ERR("query on socket port number failed"); - goto error; - } - - return ntohs(sock->info.sin6_port); - -error: - return 0; + sock->info_size = sizeof sock->info; + int query_status = getsockname(sock->sd, + (struct sockaddr *) &(sock->info), + &(sock->info_size)); + if (query_status == -1) { + ERR("query on socket port number failed"); + goto error; + } + + return ntohs(sock->info.sin6_port); + + error: + return 0; } struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp, struct http_message_t *msg) { - // Alloc packet ==---------------------------------------------------== - struct http_packet_t *pkt = packet_new(msg); - if (pkt == NULL) { - ERR("failed to create packet for incoming tcp message"); - goto error; - } - - size_t want_size = packet_pending_bytes(pkt); - if (want_size == 0) { - NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); - return pkt; - } - - struct timeval tv; - tv.tv_sec = 3; - tv.tv_usec = 0; - setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, - (char *)&tv, sizeof(struct timeval)); - - while (want_size != 0 && !msg->is_completed) { - NOTE("TCP: Getting %d bytes", want_size); - uint8_t *subbuffer = pkt->buffer + pkt->filled_size; - ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 0); - if (gotten_size < 0) { - int errno_saved = errno; - ERR("recv failed with err %d:%s", errno_saved, - strerror(errno_saved)); - tcp->is_closed = 1; - goto error; - } - NOTE("TCP: Got %d bytes", gotten_size); - if (gotten_size == 0) { - tcp->is_closed = 1; - if (pkt->filled_size == 0) { - // Client closed TCP conn - goto error; - } else { - break; - } - } - - packet_mark_received(pkt, (unsigned) gotten_size); - want_size = packet_pending_bytes(pkt); - NOTE("TCP: Want more %d bytes; Message %scompleted", want_size, msg->is_completed ? "" : "not "); - } - - NOTE("TCP: Received %lu bytes", pkt->filled_size); - return pkt; - -error: - if (pkt != NULL) - packet_free(pkt); - return NULL; + // Alloc packet ==---------------------------------------------------== + struct http_packet_t *pkt = packet_new(msg); + if (pkt == NULL) { + ERR("failed to create packet for incoming tcp message"); + goto error; + } + + size_t want_size = packet_pending_bytes(pkt); + if (want_size == 0) { + NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); + return pkt; + } + + struct timeval tv; + tv.tv_sec = 3; + tv.tv_usec = 0; + setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, + (char *)&tv, sizeof(struct timeval)); + + while (want_size != 0 && !msg->is_completed) { + NOTE("TCP: Getting %d bytes", want_size); + uint8_t *subbuffer = pkt->buffer + pkt->filled_size; + ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 0); + if (gotten_size < 0) { + int errno_saved = errno; + ERR("recv failed with err %d:%s", errno_saved, + strerror(errno_saved)); + tcp->is_closed = 1; + goto error; + } + NOTE("TCP: Got %d bytes", gotten_size); + if (gotten_size == 0) { + tcp->is_closed = 1; + if (pkt->filled_size == 0) { + // Client closed TCP conn + goto error; + } else { + break; + } + } + + packet_mark_received(pkt, (unsigned) gotten_size); + want_size = packet_pending_bytes(pkt); + NOTE("TCP: Want more %d bytes; Message %scompleted", want_size, msg->is_completed ? "" : "not "); + } + + NOTE("TCP: Received %lu bytes", pkt->filled_size); + return pkt; + + error: + if (pkt != NULL) + packet_free(pkt); + return NULL; } int tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) { - size_t remaining = pkt->filled_size; - size_t total = 0; - while (remaining > 0) { - ssize_t sent = send(conn->sd, pkt->buffer + total, - remaining, MSG_NOSIGNAL); - if (sent < 0) { - if (errno == EPIPE) { - conn->is_closed = 1; - return 0; - } - ERR("Failed to sent data over TCP"); - return -1; - } - - size_t sent_ulong = (unsigned) sent; - total += sent_ulong; - if (sent_ulong >= remaining) - remaining = 0; - else - remaining -= sent_ulong; - } - NOTE("TCP: sent %lu bytes", total); + size_t remaining = pkt->filled_size; + size_t total = 0; + while (remaining > 0) { + ssize_t sent = send(conn->sd, pkt->buffer + total, + remaining, MSG_NOSIGNAL); + if (sent < 0) { + if (errno == EPIPE) { + conn->is_closed = 1; return 0; + } + ERR("Failed to sent data over TCP"); + return -1; + } + + size_t sent_ulong = (unsigned) sent; + total += sent_ulong; + if (sent_ulong >= remaining) + remaining = 0; + else + remaining -= sent_ulong; + } + NOTE("TCP: sent %lu bytes", total); + return 0; } struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6) { - struct tcp_conn_t *conn = calloc(1, sizeof *conn); - if (conn == NULL) { - ERR("Calloc for connection struct failed"); - goto error; - } - fd_set rfds; - int retval = 0; - int nfds = 0; - FD_ZERO(&rfds); - if (sock) { - FD_SET(sock->sd, &rfds); - nfds = sock->sd; - } - if (sock6) { - FD_SET(sock6->sd, &rfds); - if (sock6->sd > nfds) - nfds = sock6->sd; - } - if (nfds == 0) { - ERR("No valid TCP socket supplied."); - goto error; - } - nfds += 1; - retval = select(nfds, &rfds, NULL, NULL, NULL); - if (g_options.terminate) - goto error; - if (retval < 1) { - ERR("Failed to open tcp connection"); - goto error; - } - if (sock && FD_ISSET(sock->sd, &rfds)) { - conn->sd = accept(sock->sd, NULL, NULL); - NOTE ("Using IPv4"); - } else if (sock6 && FD_ISSET(sock6->sd, &rfds)) { - conn->sd = accept(sock6->sd, NULL, NULL); - NOTE ("Using IPv6"); - } else { - ERR("select failed"); - goto error; - } - if (conn->sd < 0) { - ERR("accept failed"); - goto error; - } - return conn; - -error: - if (conn != NULL) - free(conn); - return NULL; + struct tcp_conn_t *conn = calloc(1, sizeof *conn); + if (conn == NULL) { + ERR("Calloc for connection struct failed"); + goto error; + } + fd_set rfds; + int retval = 0; + int nfds = 0; + FD_ZERO(&rfds); + if (sock) { + FD_SET(sock->sd, &rfds); + nfds = sock->sd; + } + if (sock6) { + FD_SET(sock6->sd, &rfds); + if (sock6->sd > nfds) + nfds = sock6->sd; + } + if (nfds == 0) { + ERR("No valid TCP socket supplied."); + goto error; + } + nfds += 1; + retval = select(nfds, &rfds, NULL, NULL, NULL); + if (g_options.terminate) + goto error; + if (retval < 1) { + ERR("Failed to open tcp connection"); + goto error; + } + if (sock && FD_ISSET(sock->sd, &rfds)) { + conn->sd = accept(sock->sd, NULL, NULL); + NOTE ("Using IPv4"); + } else if (sock6 && FD_ISSET(sock6->sd, &rfds)) { + conn->sd = accept(sock6->sd, NULL, NULL); + NOTE ("Using IPv6"); + } else { + ERR("select failed"); + goto error; + } + if (conn->sd < 0) { + ERR("accept failed"); + goto error; + } + return conn; + + error: + if (conn != NULL) + free(conn); + return NULL; } void tcp_conn_close(struct tcp_conn_t *conn) { - close(conn->sd); - free(conn); + close(conn->sd); + free(conn); } - @@ -29,14 +29,14 @@ #define BUFFER_MAX (1 << 20) struct tcp_sock_t { - int sd; - struct sockaddr_in6 info; - socklen_t info_size; + int sd; + struct sockaddr_in6 info; + socklen_t info_size; }; struct tcp_conn_t { - int sd; - int is_closed; + int sd; + int is_closed; }; struct tcp_sock_t *tcp_open(uint16_t, char* interface); @@ -34,90 +34,90 @@ static int bus, dev_addr; static int is_ippusb_interface(const struct libusb_interface_descriptor *interf) { - return interf->bInterfaceClass == 0x07 && - interf->bInterfaceSubClass == 0x01 && - interf->bInterfaceProtocol == 0x04; + return interf->bInterfaceClass == 0x07 && + interf->bInterfaceSubClass == 0x01 && + interf->bInterfaceProtocol == 0x04; } static int count_ippoverusb_interfaces(struct libusb_config_descriptor *config) { - int ippusb_interface_count = 0; + int ippusb_interface_count = 0; - NOTE("Counting IPP-over-USB interfaces ..."); - for (uint8_t interface_num = 0; - interface_num < config->bNumInterfaces; - interface_num++) { + NOTE("Counting IPP-over-USB interfaces ..."); + for (uint8_t interface_num = 0; + interface_num < config->bNumInterfaces; + interface_num++) { - const struct libusb_interface *interface = NULL; - interface = &config->interface[interface_num]; + const struct libusb_interface *interface = NULL; + interface = &config->interface[interface_num]; - for (int alt_num = 0; - alt_num < interface->num_altsetting; - alt_num++) { - - const struct libusb_interface_descriptor *alt = NULL; - alt = &interface->altsetting[alt_num]; - NOTE("Interface %d, Alt %d: Class %d, Subclass %d, Protocol %d", - interface_num, alt_num, alt->bInterfaceClass, - alt->bInterfaceSubClass, alt->bInterfaceProtocol); - - // Check for IPP over USB interfaces - if (!is_ippusb_interface(alt)) - continue; - - NOTE(" -> is IPP-over-USB"); - ippusb_interface_count++; - break; - } - } + for (int alt_num = 0; + alt_num < interface->num_altsetting; + alt_num++) { + + const struct libusb_interface_descriptor *alt = NULL; + alt = &interface->altsetting[alt_num]; + NOTE("Interface %d, Alt %d: Class %d, Subclass %d, Protocol %d", + interface_num, alt_num, alt->bInterfaceClass, + alt->bInterfaceSubClass, alt->bInterfaceProtocol); + + // Check for IPP over USB interfaces + if (!is_ippusb_interface(alt)) + continue; + + NOTE(" -> is IPP-over-USB"); + ippusb_interface_count++; + break; + } + } - NOTE(" -> %d Interfaces", ippusb_interface_count); - return ippusb_interface_count; + NOTE(" -> %d Interfaces", ippusb_interface_count); + return ippusb_interface_count; } static int is_our_device(libusb_device *dev, struct libusb_device_descriptor desc) { - static const int SERIAL_MAX = 1024; - unsigned char serial[1024]; - NOTE("Found device: VID %04x, PID %04x on Bus %03d, Device %03d", - desc.idVendor, desc.idProduct, - libusb_get_bus_number(dev), libusb_get_device_address(dev)); - if ((g_options.vendor_id && desc.idVendor != g_options.vendor_id) || - (g_options.product_id && desc.idProduct != g_options.product_id) || - (g_options.bus && - libusb_get_bus_number(dev) != g_options.bus) || - (g_options.device && - libusb_get_device_address(dev) != g_options.device)) - return 0; - - if (g_options.serial_num == NULL) - return 1; - - libusb_device_handle *handle = NULL; - int status = libusb_open(dev, &handle); - if (status != 0) { - // Device turned off or disconnected, we cannot retrieve its - // serial number any more, so we identify it via bus and device - // addresses - return (bus == libusb_get_bus_number(dev) && - dev_addr == libusb_get_device_address(dev)); - } else { - // Device is turned on and connected, read out its serial number - // and use the serial number for identification - status = libusb_get_string_descriptor_ascii(handle, - desc.iSerialNumber, - serial, SERIAL_MAX); - libusb_close(handle); - - if (status <= 0) { - WARN("Failed to get serial from device"); - return 0; - } - - return strcmp((char *)serial, - (char *)g_options.serial_num) == 0; - } + static const int SERIAL_MAX = 1024; + unsigned char serial[1024]; + NOTE("Found device: VID %04x, PID %04x on Bus %03d, Device %03d", + desc.idVendor, desc.idProduct, + libusb_get_bus_number(dev), libusb_get_device_address(dev)); + if ((g_options.vendor_id && desc.idVendor != g_options.vendor_id) || + (g_options.product_id && desc.idProduct != g_options.product_id) || + (g_options.bus && + libusb_get_bus_number(dev) != g_options.bus) || + (g_options.device && + libusb_get_device_address(dev) != g_options.device)) + return 0; + + if (g_options.serial_num == NULL) + return 1; + + libusb_device_handle *handle = NULL; + int status = libusb_open(dev, &handle); + if (status != 0) { + // Device turned off or disconnected, we cannot retrieve its + // serial number any more, so we identify it via bus and device + // addresses + return (bus == libusb_get_bus_number(dev) && + dev_addr == libusb_get_device_address(dev)); + } else { + // Device is turned on and connected, read out its serial number + // and use the serial number for identification + status = libusb_get_string_descriptor_ascii(handle, + desc.iSerialNumber, + serial, SERIAL_MAX); + libusb_close(handle); + + if (status <= 0) { + WARN("Failed to get serial from device"); + return 0; + } + + return strcmp((char *)serial, + (char *)g_options.serial_num) == 0; + } } int get_device_id(struct libusb_device_handle *handle, @@ -169,302 +169,296 @@ int get_device_id(struct libusb_device_handle *handle, struct usb_sock_t *usb_open() { - int status_lock; - struct usb_sock_t *usb = calloc(1, sizeof *usb); - int status = 1; - usb->device_id = NULL; - status = libusb_init(&usb->context); - if (status < 0) { - ERR("libusb init failed with error: %s", - libusb_error_name(status)); - goto error_usbinit; - } - - libusb_device **device_list = NULL; - ssize_t device_count = libusb_get_device_list(usb->context, &device_list); - if (device_count < 0) { - ERR("failed to get list of usb devices"); - goto error; - } - - - // Discover device and count interfaces ==---------------------------== - int selected_config = -1; - unsigned int selected_ipp_interface_count = 0; - int auto_pick = !((g_options.vendor_id && - g_options.product_id) || - g_options.serial_num || - (g_options.bus && - g_options.device)); - - libusb_device *printer_device = NULL; - - if (g_options.vendor_id || g_options.product_id) - NOTE("Searching for device: VID %04x, PID %04x", - g_options.vendor_id, g_options.product_id); - if (g_options.serial_num) - NOTE("Searching for device with serial number %s", - g_options.serial_num); - if (g_options.bus || g_options.device) - NOTE("Searching for device: Bus %03d, Device %03d", - g_options.bus, g_options.device); - if (auto_pick) - NOTE("Searching for first IPP-over-USB-capable device available"); - - for (ssize_t i = 0; i < device_count; i++) { - libusb_device *candidate = device_list[i]; - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(candidate, &desc); - - if (!is_our_device(candidate, desc)) - continue; - - // Save VID/PID for exit-on-unplug - if (g_options.vendor_id == 0) - g_options.vendor_id = desc.idVendor; - if (g_options.product_id == 0) - g_options.product_id = desc.idProduct; - - bus = libusb_get_bus_number(candidate); - dev_addr = libusb_get_device_address(candidate); - NOTE("Printer connected on bus %03d device %03d", - bus, dev_addr); - - for (uint8_t config_num = 0; - config_num < desc.bNumConfigurations; - config_num++) { - struct libusb_config_descriptor *config = NULL; - status = libusb_get_config_descriptor(candidate, - config_num, - &config); - if (status < 0) { - ERR("USB: didn't get config desc %s", - libusb_error_name(status)); - goto error; - } - - int interface_count = count_ippoverusb_interfaces(config); - libusb_free_config_descriptor(config); - if (interface_count >= 2) { - selected_config = config_num; - selected_ipp_interface_count = (unsigned) interface_count; - printer_device = candidate; - goto found_device; - } - - // CONFTEST: Two or more interfaces are required - if (interface_count == 1) { - CONF("usb device has only one ipp interface " - "in violation of standard"); - goto error; - } - - if (!auto_pick) { - ERR("No ipp-usb interfaces found"); - goto error; - } - } - } -found_device: - - if (printer_device == NULL) { - if (!auto_pick) { - ERR("No printer found by that vid, pid, serial or bus, device"); - } else { - ERR("No IPP over USB printer found"); - } - goto error; - } - - - // Open the printer ==-----------------------------------------------== - status = libusb_open(printer_device, &usb->printer); - if (status != 0) { - ERR("failed to open device"); - goto error; - } + int status_lock; + struct usb_sock_t *usb = calloc(1, sizeof *usb); + int status = 1; + usb->device_id = NULL; + status = libusb_init(&usb->context); + if (status < 0) { + ERR("libusb init failed with error: %s", + libusb_error_name(status)); + goto error_usbinit; + } + libusb_device **device_list = NULL; + ssize_t device_count = libusb_get_device_list(usb->context, &device_list); + if (device_count < 0) { + ERR("failed to get list of usb devices"); + goto error; + } - // Open every IPP-USB interface ==-----------------------------------== - usb->num_interfaces = selected_ipp_interface_count; - usb->interfaces = calloc(usb->num_interfaces, - sizeof(*usb->interfaces)); - if (usb->interfaces == NULL) { - ERR("Failed to alloc interfaces"); - goto error; - } + // Discover device and count interfaces ==---------------------------== + int selected_config = -1; + unsigned int selected_ipp_interface_count = 0; + int auto_pick = !((g_options.vendor_id && + g_options.product_id) || + g_options.serial_num || + (g_options.bus && + g_options.device)); + + libusb_device *printer_device = NULL; + + if (g_options.vendor_id || g_options.product_id) + NOTE("Searching for device: VID %04x, PID %04x", + g_options.vendor_id, g_options.product_id); + if (g_options.serial_num) + NOTE("Searching for device with serial number %s", + g_options.serial_num); + if (g_options.bus || g_options.device) + NOTE("Searching for device: Bus %03d, Device %03d", + g_options.bus, g_options.device); + if (auto_pick) + NOTE("Searching for first IPP-over-USB-capable device available"); + + for (ssize_t i = 0; i < device_count; i++) { + libusb_device *candidate = device_list[i]; + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(candidate, &desc); + + if (!is_our_device(candidate, desc)) + continue; + + // Save VID/PID for exit-on-unplug + if (g_options.vendor_id == 0) + g_options.vendor_id = desc.idVendor; + if (g_options.product_id == 0) + g_options.product_id = desc.idProduct; + + bus = libusb_get_bus_number(candidate); + dev_addr = libusb_get_device_address(candidate); + NOTE("Printer connected on bus %03d device %03d", + bus, dev_addr); + + for (uint8_t config_num = 0; + config_num < desc.bNumConfigurations; + config_num++) { + struct libusb_config_descriptor *config = NULL; + status = libusb_get_config_descriptor(candidate, + config_num, + &config); + if (status < 0) { + ERR("USB: didn't get config desc %s", + libusb_error_name(status)); + goto error; + } + + int interface_count = count_ippoverusb_interfaces(config); + libusb_free_config_descriptor(config); + if (interface_count >= 2) { + selected_config = config_num; + selected_ipp_interface_count = (unsigned) interface_count; + printer_device = candidate; + goto found_device; + } + + // CONFTEST: Two or more interfaces are required + if (interface_count == 1) { + CONF("usb device has only one ipp interface " + "in violation of standard"); + goto error; + } + + if (!auto_pick) { + ERR("No ipp-usb interfaces found"); + goto error; + } + } + } + found_device: + + if (printer_device == NULL) { + if (!auto_pick) { + ERR("No printer found by that vid, pid, serial or bus, device"); + } else { + ERR("No IPP over USB printer found"); + } + goto error; + } - struct libusb_config_descriptor *config = NULL; - status = libusb_get_config_descriptor(printer_device, - (uint8_t)selected_config, - &config); - if (status != 0 || config == NULL) { - ERR("Failed to acquire config descriptor"); - goto error; - } + // Open the printer ==-----------------------------------------------== + status = libusb_open(printer_device, &usb->printer); + if (status != 0) { + ERR("failed to open device"); + goto error; + } - unsigned int interfs = selected_ipp_interface_count; - for (uint8_t interf_num = 0; - interf_num < config->bNumInterfaces; - interf_num++) { - - const struct libusb_interface *interf = NULL; - interf = &config->interface[interf_num]; - for (int alt_num = 0; - alt_num < interf->num_altsetting; - alt_num++) { - - const struct libusb_interface_descriptor *alt = NULL; - alt = &interf->altsetting[alt_num]; - - // Get the IEE-1284 device ID - if (usb->device_id == NULL) { - usb->device_id = calloc(2048, sizeof(char)); - if (usb->device_id == NULL) { - ERR("Failed to allocate memory for the device ID"); - goto error; - } - if (get_device_id(usb->printer, selected_config, - interf_num, alt_num, - usb->device_id, 2048) != 0 || - strlen(usb->device_id) == 0) { - NOTE("Could not retrieve device ID for config #%d, interface #%d, alt setting #%d, will try with other combo ...", - selected_config, interf_num, alt_num); - free(usb->device_id); - usb->device_id = NULL; - g_options.device_id = NULL; - } else { - NOTE("USB device ID: %s", usb->device_id); - g_options.device_id = usb->device_id; - } - } - - // Skip non-IPP-USB interfaces - if (!is_ippusb_interface(alt)) - continue; - - interfs--; - - struct usb_interface *uf = usb->interfaces + interfs; - uf->interface_number = interf_num; - uf->libusb_interface_index = alt->bInterfaceNumber; - uf->interface_alt = alt_num; - - // Store interface's two endpoints - for (int end_i = 0; end_i < alt->bNumEndpoints; - end_i++) { - const struct libusb_endpoint_descriptor *end; - end = &alt->endpoint[end_i]; - - usb->max_packet_size = end->wMaxPacketSize; - - // High bit set means endpoint - // is an INPUT or IN endpoint. - uint8_t address = end->bEndpointAddress; - if (address & 0x80) - uf->endpoint_in = address; - else - uf->endpoint_out = address; - } - - status_lock = sem_init(&uf->lock, 0, 1); - if (status_lock != 0) { - ERR("Failed to create interface lock #%d", - interf_num); - goto error; - } - - break; - } - } - libusb_free_config_descriptor(config); - libusb_free_device_list(device_list, 1); + // Open every IPP-USB interface ==-----------------------------------== + usb->num_interfaces = selected_ipp_interface_count; + usb->interfaces = calloc(usb->num_interfaces, + sizeof(*usb->interfaces)); + if (usb->interfaces == NULL) { + ERR("Failed to alloc interfaces"); + goto error; + } + struct libusb_config_descriptor *config = NULL; + status = libusb_get_config_descriptor(printer_device, + (uint8_t)selected_config, + &config); + if (status != 0 || config == NULL) { + ERR("Failed to acquire config descriptor"); + goto error; + } - // Pour interfaces into pool ==--------------------------------------== - usb->num_avail = usb->num_interfaces; - usb->interface_pool = calloc(usb->num_avail, - sizeof(*usb->interface_pool)); - if (usb->interface_pool == NULL) { - ERR("Failed to alloc interface pool"); - goto error; + unsigned int interfs = selected_ipp_interface_count; + for (uint8_t interf_num = 0; + interf_num < config->bNumInterfaces; + interf_num++) { + + const struct libusb_interface *interf = NULL; + interf = &config->interface[interf_num]; + for (int alt_num = 0; + alt_num < interf->num_altsetting; + alt_num++) { + + const struct libusb_interface_descriptor *alt = NULL; + alt = &interf->altsetting[alt_num]; + + // Get the IEE-1284 device ID + if (usb->device_id == NULL) { + usb->device_id = calloc(2048, sizeof(char)); + if (usb->device_id == NULL) { + ERR("Failed to allocate memory for the device ID"); + goto error; } - for (uint32_t i = 0; i < usb->num_avail; i++) { - usb->interface_pool[i] = i; + if (get_device_id(usb->printer, selected_config, + interf_num, alt_num, + usb->device_id, 2048) != 0 || + strlen(usb->device_id) == 0) { + NOTE("Could not retrieve device ID for config #%d, interface #%d, alt setting #%d, will try with other combo ...", + selected_config, interf_num, alt_num); + free(usb->device_id); + usb->device_id = NULL; + g_options.device_id = NULL; + } else { + NOTE("USB device ID: %s", usb->device_id); + g_options.device_id = usb->device_id; } - NOTE("USB interfaces pool: %d interfaces", usb->num_avail); - + } + + // Skip non-IPP-USB interfaces + if (!is_ippusb_interface(alt)) + continue; + + interfs--; + + struct usb_interface *uf = usb->interfaces + interfs; + uf->interface_number = interf_num; + uf->libusb_interface_index = alt->bInterfaceNumber; + uf->interface_alt = alt_num; + + // Store interface's two endpoints + for (int end_i = 0; end_i < alt->bNumEndpoints; + end_i++) { + const struct libusb_endpoint_descriptor *end; + end = &alt->endpoint[end_i]; + + usb->max_packet_size = end->wMaxPacketSize; + + // High bit set means endpoint + // is an INPUT or IN endpoint. + uint8_t address = end->bEndpointAddress; + if (address & 0x80) + uf->endpoint_in = address; + else + uf->endpoint_out = address; + } + + status_lock = sem_init(&uf->lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create interface lock #%d", + interf_num); + goto error; + } + + break; + } + } + libusb_free_config_descriptor(config); + libusb_free_device_list(device_list, 1); + + // Pour interfaces into pool ==--------------------------------------== + usb->num_avail = usb->num_interfaces; + usb->interface_pool = calloc(usb->num_avail, + sizeof(*usb->interface_pool)); + if (usb->interface_pool == NULL) { + ERR("Failed to alloc interface pool"); + goto error; + } + for (uint32_t i = 0; i < usb->num_avail; i++) { + usb->interface_pool[i] = i; + } + NOTE("USB interfaces pool: %d interfaces", usb->num_avail); - // Stale lock - status_lock = sem_init(&usb->num_staled_lock, 0, 1); - if (status_lock != 0) { - ERR("Failed to create num_staled lock"); - goto error; - } + // Stale lock + status_lock = sem_init(&usb->num_staled_lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create num_staled lock"); + goto error; + } - // Pool management lock - status_lock = sem_init(&usb->pool_manage_lock, 0, 1); - if (status_lock != 0) { - ERR("Failed to create pool management lock"); - goto error; - } + // Pool management lock + status_lock = sem_init(&usb->pool_manage_lock, 0, 1); + if (status_lock != 0) { + ERR("Failed to create pool management lock"); + goto error; + } - return usb; - -error: - if (device_list != NULL) - libusb_free_device_list(device_list, 1); -error_usbinit: - if (usb != NULL) { - if (usb->context != NULL) - libusb_exit(usb->context); - if (usb->interfaces != NULL) - free(usb->interfaces); - if (usb->interface_pool != NULL) - free(usb->interface_pool); - free(usb); - } - return NULL; + return usb; + + error: + if (device_list != NULL) + libusb_free_device_list(device_list, 1); + error_usbinit: + if (usb != NULL) { + if (usb->context != NULL) + libusb_exit(usb->context); + if (usb->interfaces != NULL) + free(usb->interfaces); + if (usb->interface_pool != NULL) + free(usb->interface_pool); + free(usb); + } + return NULL; } void usb_close(struct usb_sock_t *usb) { - // Release interfaces - for (uint32_t i = 0; i < usb->num_interfaces; i++) { - int number = usb->interfaces[i].interface_number; - libusb_release_interface(usb->printer, number); - sem_destroy(&usb->interfaces[i].lock); - } + // Release interfaces + for (uint32_t i = 0; i < usb->num_interfaces; i++) { + int number = usb->interfaces[i].interface_number; + libusb_release_interface(usb->printer, number); + sem_destroy(&usb->interfaces[i].lock); + } - libusb_close(usb->printer); - if (usb != NULL) { - if (usb->context != NULL) - libusb_exit(usb->context); - sem_destroy(&usb->num_staled_lock); - if (usb->interfaces != NULL) - free(usb->interfaces); - if (usb->interface_pool != NULL) - free(usb->interface_pool); - free(usb); - } - return; + libusb_close(usb->printer); + if (usb != NULL) { + if (usb->context != NULL) + libusb_exit(usb->context); + sem_destroy(&usb->num_staled_lock); + if (usb->interfaces != NULL) + free(usb->interfaces); + if (usb->interface_pool != NULL) + free(usb->interface_pool); + free(usb); + } + return; } int usb_can_callback(struct usb_sock_t *usb) { - IGNORE(usb); + IGNORE(usb); - if (!g_options.vendor_id || - !g_options.product_id) - { - NOTE("Exit-on-unplug requires vid & pid"); - return 0; - } + if (!g_options.vendor_id || + !g_options.product_id) { + NOTE("Exit-on-unplug requires vid & pid"); + return 0; + } - int works = !!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); - if (!works) - WARN("Libusb cannot tell us when to disconnect"); - return works; + int works = !!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); + if (!works) + WARN("Libusb cannot tell us when to disconnect"); + return works; } static int LIBUSB_CALL usb_exit_on_unplug(libusb_context *context, @@ -472,438 +466,435 @@ static int LIBUSB_CALL usb_exit_on_unplug(libusb_context *context, libusb_hotplug_event event, void *call_data) { - IGNORE(context); - IGNORE(event); - IGNORE(call_data); + IGNORE(context); + IGNORE(event); + IGNORE(call_data); - NOTE("Received unplug callback"); + NOTE("Received unplug callback"); - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); - if (is_our_device(device, desc)) { + if (is_our_device(device, desc)) { - // Unregister DNS-SD for printer on Avahi - if (g_options.dnssd_data != NULL) - dnssd_shutdown(); + // Unregister DNS-SD for printer on Avahi + if (g_options.dnssd_data != NULL) + dnssd_shutdown(); - exit(0); - } + exit(0); + } - return 0; + return 0; } static void *usb_pump_events(void *user_data) { - IGNORE(user_data); - - for (;;) { - if (g_options.terminate) - return NULL; - // NOTE: This is a blocking call so - // no need for sleep() - libusb_handle_events_completed(NULL, NULL); - } + IGNORE(user_data); + + for (;;) { + if (g_options.terminate) + return NULL; + // NOTE: This is a blocking call so + // no need for sleep() + libusb_handle_events_completed(NULL, NULL); + } - return NULL; + return NULL; } void usb_register_callback(struct usb_sock_t *usb) { - IGNORE(usb); - - int status = libusb_hotplug_register_callback( - NULL, - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - // Note: libusb's enum has no default value - // a bug has been filled with libusb. - // Please switch the below line to 0 - // once the issue has been fixed in - // deployed versions of libusb - // https://github.com/libusb/libusb/issues/35 - // 0, - LIBUSB_HOTPLUG_ENUMERATE, - g_options.vendor_id, - g_options.product_id, - LIBUSB_HOTPLUG_MATCH_ANY, - &usb_exit_on_unplug, - NULL, - NULL); - if (status == LIBUSB_SUCCESS) { - pthread_t thread_handle; - pthread_create(&thread_handle, NULL, &usb_pump_events, NULL); - NOTE("Registered unplug callback"); - } else - ERR("Failed to register unplug callback"); + IGNORE(usb); + + int status = + libusb_hotplug_register_callback(NULL, + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + // Note: libusb's enum has no default value + // a bug has been filled with libusb. + // Please switch the below line to 0 + // once the issue has been fixed in + // deployed versions of libusb + // https://github.com/libusb/libusb/issues/35 + // 0, + LIBUSB_HOTPLUG_ENUMERATE, + g_options.vendor_id, + g_options.product_id, + LIBUSB_HOTPLUG_MATCH_ANY, + &usb_exit_on_unplug, + NULL, + NULL); + if (status == LIBUSB_SUCCESS) { + pthread_t thread_handle; + pthread_create(&thread_handle, NULL, &usb_pump_events, NULL); + NOTE("Registered unplug callback"); + } else + ERR("Failed to register unplug callback"); } static void usb_conn_mark_staled(struct usb_conn_t *conn) { - if (conn->is_staled) - return; + if (conn->is_staled) + return; - struct usb_sock_t *usb = conn->parent; + struct usb_sock_t *usb = conn->parent; - sem_wait(&usb->num_staled_lock); - { - usb->num_staled++; - } - sem_post(&usb->num_staled_lock); + sem_wait(&usb->num_staled_lock); + { + usb->num_staled++; + } + sem_post(&usb->num_staled_lock); - conn->is_staled = 1; + conn->is_staled = 1; } static void usb_conn_mark_moving(struct usb_conn_t *conn) { - if (!conn->is_staled) - return; + if (!conn->is_staled) + return; - struct usb_sock_t *usb = conn->parent; + struct usb_sock_t *usb = conn->parent; - sem_wait(&usb->num_staled_lock); - { - usb->num_staled--; - } - sem_post(&usb->num_staled_lock); + sem_wait(&usb->num_staled_lock); + { + usb->num_staled--; + } + sem_post(&usb->num_staled_lock); - conn->is_staled = 0; + conn->is_staled = 0; } static int usb_all_conns_staled(struct usb_sock_t *usb) { - int staled; - - sem_wait(&usb->num_staled_lock); - { - sem_wait(&usb->pool_manage_lock); - { - staled = usb->num_staled == usb->num_taken; - } - sem_post(&usb->pool_manage_lock); - } - sem_post(&usb->num_staled_lock); + int staled; + + sem_wait(&usb->num_staled_lock); + { + sem_wait(&usb->pool_manage_lock); + { + staled = usb->num_staled == usb->num_taken; + } + sem_post(&usb->pool_manage_lock); + } + sem_post(&usb->num_staled_lock); - return staled; + return staled; } struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb) { + int i; + + if (usb->num_avail <= 0) { + NOTE("All USB interfaces busy, waiting ..."); + for (i = 0; i < 30 && usb->num_avail <= 0; i ++) + usleep(100000); + if (usb->num_avail <= 0) { + ERR("Timed out waiting for a free USB interface"); + return NULL; + } + usleep(100000); + } - int i; - if (usb->num_avail <= 0) { - NOTE("All USB interfaces busy, waiting ..."); - for (i = 0; i < 30 && usb->num_avail <= 0; i ++) - usleep(100000); - if (usb->num_avail <= 0) { - ERR("Timed out waiting for a free USB interface"); - return NULL; - } - usleep(100000); - } - - struct usb_conn_t *conn = calloc(1, sizeof(*conn)); - if (conn == NULL) { - ERR("Failed to alloc space for usb connection"); - return NULL; - } + struct usb_conn_t *conn = calloc(1, sizeof(*conn)); + if (conn == NULL) { + ERR("Failed to alloc space for usb connection"); + return NULL; + } - sem_wait(&usb->pool_manage_lock); - { - conn->parent = usb; - - uint32_t slot = usb->num_taken; - - conn->interface_index = usb->interface_pool[slot]; - conn->interface = usb->interfaces + conn->interface_index; - struct usb_interface *uf = conn->interface; - - // Sanity check: Is the interface still free - if (sem_trywait(&uf->lock)) { - ERR("Interface #%d (%d) already in use!", - conn->interface_index, - uf->libusb_interface_index); - goto acquire_error; - } - - // Make kernel release interface - if (libusb_kernel_driver_active(usb->printer, - uf->libusb_interface_index) == 1) { - // Only linux supports this - // other platforms will fail - // thus we ignore the error code - // it either works or it does not - libusb_detach_kernel_driver(usb->printer, - uf->libusb_interface_index); - } - - // Claim the whole interface - int status = 0; - do { - // Spinlock-like - // Libusb does not offer a blocking call - // so we're left with a spinlock - status = libusb_claim_interface( - usb->printer, uf->libusb_interface_index); - if (status) NOTE("Failed to claim interface %d, retrying", conn->interface_index); - switch (status) { - case LIBUSB_ERROR_NOT_FOUND: - ERR("USB Interface did not exist"); - goto acquire_error; - case LIBUSB_ERROR_NO_DEVICE: - ERR("Printer was removed"); - goto acquire_error; - default: - break; - } - } while (status != 0); - - // Select the IPP-USB alt setting of the interface - libusb_set_interface_alt_setting(usb->printer, - uf->libusb_interface_index, - uf->interface_alt); - - // Take successfully acquired interface from the pool - usb->num_taken++; - usb->num_avail--; - } - sem_post(&usb->pool_manage_lock); - return conn; + sem_wait(&usb->pool_manage_lock); + { + conn->parent = usb; + + uint32_t slot = usb->num_taken; + + conn->interface_index = usb->interface_pool[slot]; + conn->interface = usb->interfaces + conn->interface_index; + struct usb_interface *uf = conn->interface; + + // Sanity check: Is the interface still free + if (sem_trywait(&uf->lock)) { + ERR("Interface #%d (%d) already in use!", + conn->interface_index, + uf->libusb_interface_index); + goto acquire_error; + } + + // Make kernel release interface + if (libusb_kernel_driver_active(usb->printer, + uf->libusb_interface_index) == 1) { + // Only linux supports this + // other platforms will fail + // thus we ignore the error code + // it either works or it does not + libusb_detach_kernel_driver(usb->printer, + uf->libusb_interface_index); + } + + // Claim the whole interface + int status = 0; + do { + // Spinlock-like + // Libusb does not offer a blocking call + // so we're left with a spinlock + status = libusb_claim_interface(usb->printer, uf->libusb_interface_index); + if (status) NOTE("Failed to claim interface %d, retrying", conn->interface_index); + switch (status) { + case LIBUSB_ERROR_NOT_FOUND: + ERR("USB Interface did not exist"); + goto acquire_error; + case LIBUSB_ERROR_NO_DEVICE: + ERR("Printer was removed"); + goto acquire_error; + default: + break; + } + } while (status != 0); + + // Select the IPP-USB alt setting of the interface + libusb_set_interface_alt_setting(usb->printer, + uf->libusb_interface_index, + uf->interface_alt); + + // Take successfully acquired interface from the pool + usb->num_taken++; + usb->num_avail--; + } + sem_post(&usb->pool_manage_lock); + return conn; acquire_error: - sem_post(&usb->pool_manage_lock); - free(conn); - return NULL; - + sem_post(&usb->pool_manage_lock); + free(conn); + return NULL; } void usb_conn_release(struct usb_conn_t *conn) { - struct usb_sock_t *usb = conn->parent; - sem_wait(&usb->pool_manage_lock); - { - int status = 0; - do { - // Spinlock-like - // Libusb does not offer a blocking call - // so we're left with a spinlock - status = libusb_release_interface(usb->printer, - conn->interface->libusb_interface_index); - if (status) NOTE("Failed to release interface %d, retrying", conn->interface_index); - } while (status != 0); - - // Return usb interface to pool - usb->num_taken--; - usb->num_avail++; - uint32_t slot = usb->num_taken; - usb->interface_pool[slot] = conn->interface_index; - - // Release our interface lock - sem_post(&conn->interface->lock); - - free(conn); - } - sem_post(&usb->pool_manage_lock); + struct usb_sock_t *usb = conn->parent; + sem_wait(&usb->pool_manage_lock); + { + int status = 0; + do { + // Spinlock-like + // Libusb does not offer a blocking call + // so we're left with a spinlock + status = libusb_release_interface(usb->printer, + conn->interface->libusb_interface_index); + if (status) NOTE("Failed to release interface %d, retrying", conn->interface_index); + } while (status != 0); + + // Return usb interface to pool + usb->num_taken--; + usb->num_avail++; + uint32_t slot = usb->num_taken; + usb->interface_pool[slot] = conn->interface_index; + + // Release our interface lock + sem_post(&conn->interface->lock); + + free(conn); + } + sem_post(&usb->pool_manage_lock); } int usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) { - int size_sent = 0; - const int timeout = 1000; // 1 sec - int num_timeouts = 0; - size_t sent = 0; - size_t pending = pkt->filled_size; - while (pending > 0) { - int to_send = (int)pending; - - NOTE("P %p: USB: want to send %d bytes", pkt, to_send); - int status = libusb_bulk_transfer(conn->parent->printer, - conn->interface->endpoint_out, - pkt->buffer + sent, to_send, - &size_sent, timeout); - if (status == LIBUSB_ERROR_NO_DEVICE) { - ERR("P %p: Printer has been disconnected", - pkt); - return -1; - } - if (status == LIBUSB_ERROR_TIMEOUT) { - NOTE("P %p: USB: send timed out, retrying", pkt); - - if (num_timeouts++ > PRINTER_CRASH_TIMEOUT_RECEIVE) { - ERR("P %p: Usb send fully timed out", - pkt); - return -1; - } - - // Sleep for tenth of a second - struct timespec sleep_dur; - sleep_dur.tv_sec = 0; - sleep_dur.tv_nsec = 100000000; - nanosleep(&sleep_dur, NULL); - if (size_sent == 0) - continue; - } else if (status < 0) { - ERR("P %p: USB: send failed with status %s", - pkt, libusb_error_name(status)); - return -1; - } - if (size_sent < 0) { - ERR("P %p: Unexpected negative size_sent", - pkt); - return -1; - } - - pending -= (size_t) size_sent; - sent += (size_t) size_sent; - NOTE("P %p: USB: sent %d bytes", pkt, size_sent); - } - NOTE("P %p: USB: sent %d bytes in total", pkt, sent); - return 0; + int size_sent = 0; + const int timeout = 1000; // 1 sec + int num_timeouts = 0; + size_t sent = 0; + size_t pending = pkt->filled_size; + while (pending > 0) { + int to_send = (int)pending; + + NOTE("P %p: USB: want to send %d bytes", pkt, to_send); + int status = libusb_bulk_transfer(conn->parent->printer, + conn->interface->endpoint_out, + pkt->buffer + sent, to_send, + &size_sent, timeout); + if (status == LIBUSB_ERROR_NO_DEVICE) { + ERR("P %p: Printer has been disconnected", + pkt); + return -1; + } + if (status == LIBUSB_ERROR_TIMEOUT) { + NOTE("P %p: USB: send timed out, retrying", pkt); + + if (num_timeouts++ > PRINTER_CRASH_TIMEOUT_RECEIVE) { + ERR("P %p: Usb send fully timed out", + pkt); + return -1; + } + + // Sleep for tenth of a second + struct timespec sleep_dur; + sleep_dur.tv_sec = 0; + sleep_dur.tv_nsec = 100000000; + nanosleep(&sleep_dur, NULL); + if (size_sent == 0) + continue; + } else if (status < 0) { + ERR("P %p: USB: send failed with status %s", + pkt, libusb_error_name(status)); + return -1; + } + if (size_sent < 0) { + ERR("P %p: Unexpected negative size_sent", + pkt); + return -1; + } + + pending -= (size_t) size_sent; + sent += (size_t) size_sent; + NOTE("P %p: USB: sent %d bytes", pkt, size_sent); + } + NOTE("P %p: USB: sent %d bytes in total", pkt, sent); + return 0; } struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) { - if (msg->is_completed) - return NULL; + if (msg->is_completed) + return NULL; - struct http_packet_t *pkt = packet_new(msg); - if (pkt == NULL) { - ERR("failed to create packet for incoming usb message"); - goto cleanup; - } + struct http_packet_t *pkt = packet_new(msg); + if (pkt == NULL) { + ERR("failed to create packet for incoming usb message"); + goto cleanup; + } - // File packet - const int timeout = 1000; // 1 sec - size_t read_size_ulong = packet_pending_bytes(pkt); - if (read_size_ulong == 0) - return pkt; - - uint64_t times_staled = 0; - while (read_size_ulong > 0 && !msg->is_completed) { - if (read_size_ulong >= INT_MAX) - goto cleanup; - int read_size = (int)read_size_ulong; - - // Pad read_size to multiple of usb's max packet size - read_size += (512 - (read_size % 512)) % 512; - - // Expand buffer if needed - if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) - if (packet_expand(pkt) < 0) { - ERR("Failed to ensure room for usb pkt"); - goto cleanup; - } - - int gotten_size = 0; - int status = libusb_bulk_transfer( - conn->parent->printer, + // File packet + const int timeout = 1000; // 1 sec + size_t read_size_ulong = packet_pending_bytes(pkt); + if (read_size_ulong == 0) + return pkt; + + uint64_t times_staled = 0; + while (read_size_ulong > 0 && !msg->is_completed) { + if (read_size_ulong >= INT_MAX) + goto cleanup; + int read_size = (int)read_size_ulong; + + // Pad read_size to multiple of usb's max packet size + read_size += (512 - (read_size % 512)) % 512; + + // Expand buffer if needed + if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) + if (packet_expand(pkt) < 0) { + ERR("Failed to ensure room for usb pkt"); + goto cleanup; + } + + int gotten_size = 0; + int status = libusb_bulk_transfer(conn->parent->printer, conn->interface->endpoint_in, pkt->buffer + pkt->filled_size, read_size, &gotten_size, timeout); - if (status == LIBUSB_ERROR_NO_DEVICE) { - ERR("Printer has been disconnected"); - goto cleanup; - } - - if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { - ERR("bulk xfer failed with error code %d", status); - ERR("tried reading %d bytes", read_size); - goto cleanup; - } else if (status == LIBUSB_ERROR_TIMEOUT) { - ERR("bulk xfer timed out, retrying ..."); - ERR("tried reading %d bytes, actually read %d bytes", - read_size, gotten_size); - } - - if (gotten_size < 0) { - ERR("Negative read size unexpected"); - goto cleanup; - } - - if (gotten_size > 0) { - times_staled = 0; - usb_conn_mark_moving(conn); - } else { - - // Performance Test --------------- - // How long we sleep here has a - // dramatic affect on how long it - // takes to load a page. - // Earlier versions waited a tenth - // of a second which resulted in - // minute long page loads. - // On my HP printer the most obvious - // bottleneck is the "Unified.js" file - // which weighs 517.87KB. My profiling - // looked at how shortening this sleep - // could improve this file's load times. - // The cycle count is from perf and - // covers an entire page load. - // - // Below are my results: - // 1 in 100 == 2447ms, 261M cycles - // 1 in 1,000 == 483ms, 500M cycles - // 5 in 10,000 == 433ms, 800M cycles - // 1 in 10,000 == 320ms, 3000M cycles - #define TIMEOUT_RATIO (10000 / 5) - static uint64_t stale_timeout = - CONN_STALE_THRESHHOLD * TIMEOUT_RATIO; - static uint64_t crash_timeout = - PRINTER_CRASH_TIMEOUT_ANSWER * TIMEOUT_RATIO; - static uint64_t skip_timeout = - 1000000000 / TIMEOUT_RATIO; - - struct timespec sleep_dur; - sleep_dur.tv_sec = 0; - sleep_dur.tv_nsec = skip_timeout; - nanosleep(&sleep_dur, NULL); - - if (status == LIBUSB_ERROR_TIMEOUT) - times_staled += TIMEOUT_RATIO * timeout / 1000; - else - times_staled++; - if (times_staled % TIMEOUT_RATIO == 0 || - status == LIBUSB_ERROR_TIMEOUT) { - NOTE("No bytes received for %d sec.", - times_staled / TIMEOUT_RATIO); - if (pkt->filled_size > 0) - NOTE("Packet so far \n===\n%s===\n", - hexdump(pkt->buffer, - pkt->filled_size)); - } + if (status == LIBUSB_ERROR_NO_DEVICE) { + ERR("Printer has been disconnected"); + goto cleanup; + } + + if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { + ERR("bulk xfer failed with error code %d", status); + ERR("tried reading %d bytes", read_size); + goto cleanup; + } else if (status == LIBUSB_ERROR_TIMEOUT) { + ERR("bulk xfer timed out, retrying ..."); + ERR("tried reading %d bytes, actually read %d bytes", + read_size, gotten_size); + } + + if (gotten_size < 0) { + ERR("Negative read size unexpected"); + goto cleanup; + } + + if (gotten_size > 0) { + times_staled = 0; + usb_conn_mark_moving(conn); + } else { + + // Performance Test --------------- + // How long we sleep here has a + // dramatic affect on how long it + // takes to load a page. + // Earlier versions waited a tenth + // of a second which resulted in + // minute long page loads. + // On my HP printer the most obvious + // bottleneck is the "Unified.js" file + // which weighs 517.87KB. My profiling + // looked at how shortening this sleep + // could improve this file's load times. + // The cycle count is from perf and + // covers an entire page load. + // + // Below are my results: + // 1 in 100 == 2447ms, 261M cycles + // 1 in 1,000 == 483ms, 500M cycles + // 5 in 10,000 == 433ms, 800M cycles + // 1 in 10,000 == 320ms, 3000M cycles + #define TIMEOUT_RATIO (10000 / 5) + static uint64_t stale_timeout = + CONN_STALE_THRESHHOLD * TIMEOUT_RATIO; + static uint64_t crash_timeout = + PRINTER_CRASH_TIMEOUT_ANSWER * TIMEOUT_RATIO; + static uint64_t skip_timeout = + 1000000000 / TIMEOUT_RATIO; + + struct timespec sleep_dur; + sleep_dur.tv_sec = 0; + sleep_dur.tv_nsec = skip_timeout; + nanosleep(&sleep_dur, NULL); + + if (status == LIBUSB_ERROR_TIMEOUT) + times_staled += TIMEOUT_RATIO * timeout / 1000; + else + times_staled++; + if (times_staled % TIMEOUT_RATIO == 0 || + status == LIBUSB_ERROR_TIMEOUT) { + NOTE("No bytes received for %d sec.", + times_staled / TIMEOUT_RATIO); + if (pkt->filled_size > 0) + NOTE("Packet so far \n===\n%s===\n", + hexdump(pkt->buffer, + pkt->filled_size)); + } - if (times_staled > stale_timeout) { - usb_conn_mark_staled(conn); - - if (pkt->filled_size > 0 || - usb_all_conns_staled(conn->parent) || - times_staled > crash_timeout) { - ERR("USB timed out, giving up waiting for more data"); - break; - } - } - } - - if (gotten_size) { - NOTE("USB: Getting %d bytes of %d", - read_size, pkt->expected_size); - NOTE("USB: Got %d bytes", gotten_size); - } - packet_mark_received(pkt, (size_t)gotten_size); - read_size_ulong = packet_pending_bytes(pkt); + if (times_staled > stale_timeout) { + usb_conn_mark_staled(conn); + + if (pkt->filled_size > 0 || + usb_all_conns_staled(conn->parent) || + times_staled > crash_timeout) { + ERR("USB timed out, giving up waiting for more data"); + break; } - NOTE("USB: Received %d bytes of %d with type %d", - pkt->filled_size, pkt->expected_size, msg->type); + } + } + + if (gotten_size) { + NOTE("USB: Getting %d bytes of %d", + read_size, pkt->expected_size); + NOTE("USB: Got %d bytes", gotten_size); + } + packet_mark_received(pkt, (size_t)gotten_size); + read_size_ulong = packet_pending_bytes(pkt); + } + NOTE("USB: Received %d bytes of %d with type %d", + pkt->filled_size, pkt->expected_size, msg->type); - if (pkt->filled_size == 0) - goto cleanup; + if (pkt->filled_size == 0) + goto cleanup; - return pkt; + return pkt; -cleanup: - if (pkt != NULL) - packet_free(pkt); - return NULL; + cleanup: + if (pkt != NULL) + packet_free(pkt); + return NULL; } @@ -23,39 +23,38 @@ #define CONN_STALE_THRESHHOLD 5 struct usb_interface { - uint8_t interface_number; - uint8_t libusb_interface_index; - int interface_alt; - uint8_t endpoint_in; - uint8_t endpoint_out; - sem_t lock; + uint8_t interface_number; + uint8_t libusb_interface_index; + int interface_alt; + uint8_t endpoint_in; + uint8_t endpoint_out; + sem_t lock; }; struct usb_sock_t { - libusb_context *context; - libusb_device_handle *printer; - char *device_id; - int max_packet_size; + libusb_context *context; + libusb_device_handle *printer; + char *device_id; + int max_packet_size; - uint32_t num_interfaces; - struct usb_interface *interfaces; + uint32_t num_interfaces; + struct usb_interface *interfaces; - uint32_t num_staled; - sem_t num_staled_lock; + uint32_t num_staled; + sem_t num_staled_lock; - sem_t pool_manage_lock; - uint32_t num_avail; - uint32_t num_taken; - - uint32_t *interface_pool; + sem_t pool_manage_lock; + uint32_t num_avail; + uint32_t num_taken; + uint32_t *interface_pool; }; struct usb_conn_t { - struct usb_sock_t *parent; - struct usb_interface *interface; - uint32_t interface_index; - int is_staled; + struct usb_sock_t *parent; + struct usb_interface *interface; + uint32_t interface_index; + int is_staled; }; struct usb_sock_t *usb_open(void); @@ -69,4 +68,3 @@ void usb_conn_release(struct usb_conn_t *); int usb_conn_packet_send(struct usb_conn_t *, struct http_packet_t *); struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *, struct http_message_t *); - |