{"id":"e105969c454a8da9","repo":"hyperium/hyper","slug":"invalid-chunk-size-line-missing-size-digit","errorCode":null,"errorMessage":"Invalid chunk size line: missing size digit","messagePattern":"Invalid chunk size line: missing size digit","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h1/decode.rs","lineNumber":364,"sourceCode":"    ) -> Poll<Result<ChunkedState, io::Error>> {\n        trace!(\"Read chunk start\");\n\n        let radix = 16;\n        match byte!(rdr, cx) {\n            b @ b'0'..=b'9' => {\n                *size = or_overflow!(size.checked_mul(radix));\n                *size = or_overflow!(size.checked_add(u64::from(b - b'0')));\n            }\n            b @ b'a'..=b'f' => {\n                *size = or_overflow!(size.checked_mul(radix));\n                *size = or_overflow!(size.checked_add(u64::from(b + 10 - b'a')));\n            }\n            b @ b'A'..=b'F' => {\n                *size = or_overflow!(size.checked_mul(radix));\n                *size = or_overflow!(size.checked_add(u64::from(b + 10 - b'A')));\n            }\n            _ => {\n                return Poll::Ready(Err(io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    \"Invalid chunk size line: missing size digit\",\n                )));\n            }\n        }\n\n        Poll::Ready(Ok(ChunkedState::Size))\n    }\n\n    fn read_size<R: MemRead>(\n        cx: &mut Context<'_>,\n        rdr: &mut R,\n        size: &mut u64,\n    ) -> Poll<Result<ChunkedState, io::Error>> {\n        trace!(\"Read chunk hex size\");\n\n        let radix = 16;\n        match byte!(rdr, cx) {","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h1/decode.rs#L346-L382","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: writing raw data into a chunked response\nwrite!(w, \"{{\\\"ok\\\":true}}\").await?; // peer sees no hex size -> error 20\n\n// after: emit proper chunk framing\nlet payload = b\"{\\\"ok\\\":true}\";\nwrite!(w, \"{:x}\\r\\n\", payload.len()).await?;\nw.write_all(payload).await?;\nwrite!(w, \"\\r\\n0\\r\\n\\r\\n\").await?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// hyper surfaces decoder errors as hyper::Error when polling the body.\nuse hyper::body::Body;\nlet mut body = resp.into_body();\nwhile let Some(chunk) = body.data().await {\n    match chunk {\n        Ok(bytes) => { /* accumulate */ }\n        Err(e) => {\n            let kind = e.source()\n                .and_then(|s| s.downcast_ref::<std::io::Error>())\n                .map(|io| io.kind());\n            if matches!(kind, Some(std::io::ErrorKind::InvalidInput)) {\n                // peer sent malformed chunked framing (e.g. non-hex leading byte)\n                tracing::warn!(error=%e, \"malformed chunk size line; dropping stream\");\n                break;\n            }\n            return Err(e.into());\n        }\n    }\n}","preventionTips":["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."],"tags":["http","http1","chunked","framing","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}