hyperium/hyper · error · std::io::Error

Invalid chunk body CR

Error message

Invalid chunk body CR

What it means

Thrown in read_body_cr (src/proto/h1/decode.rs:511) after reading exactly chunk_len bytes of chunk data: the next byte must be '\r' (start of the trailing CRLF). Any other byte yields io::ErrorKind::InvalidInput, meaning the chunk's data ran long or short relative to its announced size.

Source

Thrown at src/proto/h1/decode.rs:511

                IncompleteBody,
            )));
        }
        *buf = Some(slice);
        *rem -= count as u64;

        if *rem > 0 {
            Poll::Ready(Ok(ChunkedState::Body))
        } else {
            Poll::Ready(Ok(ChunkedState::BodyCr))
        }
    }
    fn read_body_cr<R: MemRead>(
        cx: &mut Context<'_>,
        rdr: &mut R,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr, cx) {
            b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk body CR",
            ))),
        }
    }
    fn read_body_lf<R: MemRead>(
        cx: &mut Context<'_>,
        rdr: &mut R,
    ) -> Poll<Result<ChunkedState, io::Error>> {
        match byte!(rdr, cx) {
            b'\n' => Poll::Ready(Ok(ChunkedState::Start)),
            _ => Poll::Ready(Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Invalid chunk body LF",
            ))),
        }
    }

View on GitHub (pinned to 084473f728)

Solutions

  1. Verify the announced hex size equals the exact number of data bytes before the CRLF.
  2. If you compress, chunk after compression (or recompute the length) so counts stay consistent.
  3. Serialize/stream body writes from a single task so sizes and data can't drift apart.

Example fix

// before: announcing a fixed size then streaming variable data
write!(w, "10\r\n").await?;
w.write_all(&maybe_seventeen_bytes).await?; // -> error 27
write!(w, "\r\n").await?;

// after: announce the real length
write!(w, "{:x}\r\n", data.len()).await?;
w.write_all(&data).await?;
write!(w, "\r\n").await?;
Defensive patterns

Strategy: try-catch

Try / catch

Some(Err(e)) => {
    let kind = e.source()
        .and_then(|s| s.downcast_ref::<std::io::Error>())
        .map(|io| io.kind());
    if matches!(kind, Some(std::io::ErrorKind::InvalidInput)) {
        tracing::warn!(error=%e, "chunk body not followed by CRLF (size/data mismatch)");
        break;
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: A chunk that announced "10\r\n" (16 bytes) but whose 17th byte is not '\r' — e.g. "10\r\n1234567890abcdefX\r\n" — because the sender wrote 17 bytes, or miscounted and the CRLF landed at the wrong offset.

Common situations: Off-by-one in a custom chunker; concurrent writes to the body stream that interleave data of different lengths than announced; compression applied after chunking so byte counts drift.

Related errors


AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06). Data as JSON: /data/errors/74121a45846e47da.json. Report an issue: GitHub.