From 078afca1b3c18fab14500359850ed5b44a6bfc55 Mon Sep 17 00:00:00 2001 From: Alexander Pevzner Date: Sun, 8 Dec 2024 18:55:14 +0300 Subject: [PATCH] Added 'zlp-recv-hack' quirk Some enterprise-level HP devices, during the initialization phase (which can last several minutes), may respond with an HTTP 503 status or similar, which is expected. However, the response body may be truncated (typically, the terminating '\n' is lost). In such cases, `ipp-usb` will wait indefinitely for a response to maintain synchronization with the device. At the same time, these devices send a zero-length UDP packet at the end of the truncated output. If the zlp-recv-hack quirk is enabled, when ipp-usb receives a zero-length packet from the USB followed by a receive timeout, it interprets this combination of events as a valid termination of the response body. It works only at the initialization time and doesn't affect futher operations. See discussion under #64 for details. Related issues are #83 and #32. --- ipp-usb.8 | 6 ++++++ ipp-usb.8.md | 15 +++++++++++++++ quirks.go | 9 +++++++++ quirks_test.go | 11 +++++++++++ usbtransport.go | 12 ++++++++++++ 5 files changed, 53 insertions(+) diff --git a/ipp-usb.8 b/ipp-usb.8 index a82546e..cd038f0 100644 --- a/ipp-usb.8 +++ b/ipp-usb.8 @@ -308,6 +308,12 @@ Delay between subsequent requests\. .br Don't use more that N USB interfaces, even if more is available\. .IP "\(bu" 4 +\fBzlp\-recv\-hack = true | false\fR +.br +Some enterprise\-level HP devices, during the initialization phase (which can last several minutes), may respond with an HTTP 503 status or similar, which is expected\. However, the response body may be truncated (typically, the terminating '\en' is lost)\. In such cases, \fBipp\-usb\fR will wait indefinitely for a response to maintain synchronization with the device\. +.IP +At the same time, these devices send a zero\-length UDP packet at the end of the truncated output\. If the \fBzlp\-recv\-hack\fR quirk is enabled, when ipp\-usb receives a zero\-length packet from the USB followed by a receive timeout, it interprets this combination of events as a valid termination of the response body\. It works only at the initialization time and doesn't affect futher operations\. +.IP "\(bu" 4 \fBzlp\-send = true | false\fR .br Terminate outgoing transfers that a multiple of the endpoint's packet size win an extra zero length packet\. diff --git a/ipp-usb.8.md b/ipp-usb.8.md index 7182e27..6361077 100644 --- a/ipp-usb.8.md +++ b/ipp-usb.8.md @@ -387,6 +387,21 @@ The following parameters are defined: * `usb-max-interfaces = N`
Don't use more that N USB interfaces, even if more is available. + * `zlp-recv-hack = true | false`
+ Some enterprise-level HP devices, during the initialization phase + (which can last several minutes), may respond with an HTTP 503 + status or similar, which is expected. However, the response body may + be truncated (typically, the terminating '\n' is lost). In such + cases, `ipp-usb` will wait indefinitely for a response to maintain + synchronization with the device. + + At the same time, these devices send a zero-length UDP packet at the + end of the truncated output. If the `zlp-recv-hack` quirk is enabled, + when ipp-usb receives a zero-length packet from the USB followed by + a receive timeout, it interprets this combination of events as a + valid termination of the response body. It works only at the + initialization time and doesn't affect futher operations. + * `zlp-send = true | false`
Terminate outgoing transfers that a multiple of the endpoint's packet size win an extra zero length packet. diff --git a/quirks.go b/quirks.go index 63bb02f..f4c43a4 100644 --- a/quirks.go +++ b/quirks.go @@ -44,6 +44,7 @@ const ( QuirkNmInitTimeout = "init-timeout" QuirkNmRequestDelay = "request-delay" QuirkNmUsbMaxInterfaces = "usb-max-interfaces" + QuirkNmZlpRecvHack = "zlp-recv-hack" QuirkNmZlpSend = "zlp-send" ) @@ -59,6 +60,7 @@ var quirkParse = map[string]func(*Quirk) error{ QuirkNmInitTimeout: (*Quirk).parseDuration, QuirkNmRequestDelay: (*Quirk).parseDuration, QuirkNmUsbMaxInterfaces: (*Quirk).parseUint, + QuirkNmZlpRecvHack: (*Quirk).parseBool, QuirkNmZlpSend: (*Quirk).parseBool, } @@ -74,6 +76,7 @@ var quirkDefaultStrings = map[string]string{ QuirkNmInitTimeout: DevInitTimeout.String(), QuirkNmRequestDelay: "0", QuirkNmUsbMaxInterfaces: "0", + QuirkNmZlpRecvHack: "false", QuirkNmZlpSend: "false", } @@ -347,6 +350,12 @@ func (quirks Quirks) GetUsbMaxInterfaces() uint { return quirks.Get(QuirkNmUsbMaxInterfaces).Parsed.(uint) } +// GetZlpRecvHack returns effective "zlp-send" parameter, +// taking the whole set into consideration. +func (quirks Quirks) GetZlpRecvHack() bool { + return quirks.Get(QuirkNmZlpRecvHack).Parsed.(bool) +} + // GetZlpSend returns effective "zlp-send" parameter, // taking the whole set into consideration. func (quirks Quirks) GetZlpSend() bool { diff --git a/quirks_test.go b/quirks_test.go index 0835034..7a23095 100644 --- a/quirks_test.go +++ b/quirks_test.go @@ -135,6 +135,17 @@ func TestQuirksLookup(t *testing.T) { origin: "default", }, + { + model: "Unknown Device", + param: QuirkNmZlpRecvHack, + get: func(quirks Quirks) interface{} { + return quirks.GetZlpRecvHack() + }, + match: "*", + value: false, + origin: "default", + }, + { model: "Unknown Device", param: QuirkNmZlpSend, diff --git a/usbtransport.go b/usbtransport.go index cec736a..edbcc68 100644 --- a/usbtransport.go +++ b/usbtransport.go @@ -750,6 +750,10 @@ func (conn *usbConn) Read(b []byte) (int, error) { b = b[0:n] } + // zlp-recv-hack handling + zlpRecvHack := conn.transport.quirks.GetZlpRecvHack() + zlpRecv := false + // Setup deadline backoff := time.Millisecond * 10 for { @@ -767,6 +771,13 @@ func (conn *usbConn) Read(b []byte) (int, error) { "USB[%d]: recv: %s", conn.index, err) if err == context.DeadlineExceeded { + // If we've got read timeout preceded + // by the zero-length packet, interpret + // is as body EOF condition + if zlpRecvHack && zlpRecv { + return 0, io.EOF + } + atomic.StoreUint32( &conn.transport.timeoutExpired, 1) } @@ -776,6 +787,7 @@ func (conn *usbConn) Read(b []byte) (int, error) { return n, err } + zlpRecv = true conn.transport.log.Debug(' ', "USB[%d]: zero-size read", conn.index)