hyperium/hyper · error · std::io::Error
Invalid chunk size line: missing size digit
Error message
Invalid chunk size line: missing size digit
What it means
Thrown by hyper's HTTP/1 chunked decoder in the Start state (src/proto/h1/decode.rs:364) when the very first byte of a chunk-size line is not a hexadecimal digit (0-9, a-f, A-F). The chunked transfer-encoding grammar requires each chunk to begin with a hex size token, so a non-hex leading byte means the peer sent malformed framing. It is reported as io::ErrorKind::InvalidInput and surfaces on a Body poll as a hyper::Error, aborting the body stream.
Source
Thrown at src/proto/h1/decode.rs:364
) -> Poll<Result<ChunkedState, io::Error>> {
trace!("Read chunk start");
let radix = 16;
match byte!(rdr, cx) {
b @ b'0'..=b'9' => {
*size = or_overflow!(size.checked_mul(radix));
*size = or_overflow!(size.checked_add(u64::from(b - b'0')));
}
b @ b'a'..=b'f' => {
*size = or_overflow!(size.checked_mul(radix));
*size = or_overflow!(size.checked_add(u64::from(b + 10 - b'a')));
}
b @ b'A'..=b'F' => {
*size = or_overflow!(size.checked_mul(radix));
*size = or_overflow!(size.checked_add(u64::from(b + 10 - b'A')));
}
_ => {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size line: missing size digit",
)));
}
}
Poll::Ready(Ok(ChunkedState::Size))
}
fn read_size<R: MemRead>(
cx: &mut Context<'_>,
rdr: &mut R,
size: &mut u64,
) -> Poll<Result<ChunkedState, io::Error>> {
trace!("Read chunk hex size");
let radix = 16;
match byte!(rdr, cx) {View on GitHub (pinned to 084473f728)
Solutions
- Capture the raw bytes off the wire and confirm each chunk begins with a hex size token (e.g. "1a\r\n<data>\r\n").
- If you own the sender, write the hex length token before each chunk's data, a trailing CRLF, and a final "0\r\n\r\n".
- Check any proxy/load-balancer between you and the peer for incorrect (re|de)-chunking.
- If the body is not meant to be streamed in chunks, send Content-Length instead of Transfer-Encoding: chunked.
Example fix
// before: writing raw data into a chunked response
write!(w, "{{\"ok\":true}}").await?; // peer sees no hex size -> error 20
// after: emit proper chunk framing
let payload = b"{\"ok\":true}";
write!(w, "{:x}\r\n", payload.len()).await?;
w.write_all(payload).await?;
write!(w, "\r\n0\r\n\r\n").await?; Defensive patterns
Strategy: try-catch
Try / catch
// hyper surfaces decoder errors as hyper::Error when polling the body.
use hyper::body::Body;
let mut body = resp.into_body();
while let Some(chunk) = body.data().await {
match chunk {
Ok(bytes) => { /* accumulate */ }
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)) {
// peer sent malformed chunked framing (e.g. non-hex leading byte)
tracing::warn!(error=%e, "malformed chunk size line; dropping stream");
break;
}
return Err(e.into());
}
}
} Prevention
- Never hand-roll chunked framing; use hyper's Body/stream helpers so hex size + CRLF are written correctly.
- When generating chunked bodies, write the size line as a single formatted call: write!(w, "{:x}\r\n", len).
- Add an integration test that decodes your own chunked output with hyper before shipping.
When it happens
Trigger: A chunked body whose chunk-size line begins with a non-hex byte: a bare "\r\n\r\n" at the start, a stray '\n', or a body that was never chunk-encoded but whose headers declared Transfer-Encoding: chunked (e.g. plain JSON streamed straight into a chunked response with no hex prefix).
Common situations: Hand-rolling a chunked response without writing the hex size prefix; an upstream proxy that strips or rewrites chunk framing; a buggy peer that emits an empty chunk line; mismatch between declared Transfer-Encoding and the actual bytes after a middleware or version change.
Related errors
- Invalid chunk size line: Invalid Size
- Invalid chunk size linear white space
- Invalid chunk size LF
- Invalid chunk body CR
- Invalid chunk body LF
AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06).
Data as JSON: /data/errors/e105969c454a8da9.json.
Report an issue: GitHub.