stalwartlabs/stalwart · warning · io::Error(InvalidData)

Invalid response: {raw_response}

Error message

Invalid response: {raw_response}

What it means

Raised when a Pyzor UDP response parsed successfully but one or more required fields (code, count, wl-count) are missing, leaving them at their sentinel values u32::MAX / u64::MAX. pyzor_send_message treats an incomplete result as invalid and includes the whole raw payload in the error message.

Source

Thrown at crates/spam-filter/src/modules/pyzor.rs:134

                        std::io::ErrorKind::InvalidData,
                        format!("Invalid line: {raw_response}"),
                    )
                })?;
            } else if k.eq_ignore_ascii_case("wl-count") {
                response.wl_count = v.trim().parse().map_err(|_| {
                    std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        format!("Invalid line: {raw_response}"),
                    )
                })?;
            }
        }
    }

    if response.code != u32::MAX && response.count != u64::MAX && response.wl_count != u64::MAX {
        Ok(response)
    } else {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            format!("Invalid response: {raw_response}"),
        ))
    }
}

trait PyzorWrite {
    fn write_all(&mut self, data: &[u8]);
}

impl PyzorWrite for Vec<u8> {
    fn write_all(&mut self, data: &[u8]) {
        self.extend_from_slice(data);
    }
}

impl PyzorWrite for Sha1 {
    fn write_all(&mut self, data: &[u8]) {

View on GitHub (pinned to e962003857)

Solutions

  1. Verify the Pyzor server address/port configuration matches a genuine Pyzor server
  2. Probe the server manually and inspect the complete raw response
  3. Log raw_response from the error to determine which fields are missing
  4. Switch to a healthy Pyzor mirror or upgrade the server
  5. Handle the PyzorError event in pyzor_check so spam scoring degrades gracefully
Defensive patterns

Strategy: validation

Validate before calling

// check the reply contains all required fields before use
fn is_complete_pyzor_response(raw: &str) -> bool {
    let has = |key: &str| raw.lines().any(|l| {
        l.split_once(':').map(|(k, _)| k.eq_ignore_ascii_case(key)).unwrap_or(false)
    });
    has("code") && has("count") && has("wl-count")
}

Try / catch

match pyzor_check(&config, &message).await {
    Ok(score) => score,
    Err(e) if e.details().contains("Invalid response") => {
        tracing::warn!("incomplete pyzor reply; skipping signal");
        Default::default()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: pyzor_send_message receives a valid UTF-8 datagram that lacks code:, count:, or wl-count: lines — e.g. an empty datagram, a bare error string, or a partial protocol reply.

Common situations: Overloaded or degraded Pyzor server returning partial replies; wrong port hitting a service that responds but not in Pyzor protocol; datagram truncation losing trailing fields; server rejecting a digest without full counts.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/d89c17fa41e66b7f. Report an issue: GitHub.