{"record":{"id":"e081f264cf53eb03","repo":"python/cpython","slug":"readexactly-size-can-not-be-less-than-zero","errorCode":null,"errorMessage":"readexactly size can not be less than zero","messagePattern":"readexactly size can not be less than zero","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/streams.py","lineNumber":752,"sourceCode":"        return data\n\n    async def readexactly(self, n):\n        \"\"\"Read exactly `n` bytes.\n\n        Raise an IncompleteReadError if EOF is reached before `n` bytes can be\n        read. The IncompleteReadError.partial attribute of the exception will\n        contain the partial read bytes.\n\n        if n is zero, return empty bytes object.\n\n        Returned value is not limited with limit, configured at stream\n        creation.\n\n        If stream was paused, this function will automatically resume it if\n        needed.\n        \"\"\"\n        if n < 0:\n            raise ValueError('readexactly size can not be less than zero')\n\n        if self._exception is not None:\n            raise self._exception\n\n        if n == 0:\n            return b''\n\n        while len(self._buffer) < n:\n            if self._eof:\n                incomplete = self._buffer.take_bytes()\n                raise exceptions.IncompleteReadError(incomplete, n)\n\n            await self._wait_for_data('readexactly')\n\n        data = self._buffer.take_bytes(n)\n        self._maybe_resume_transport()\n        return data\n","sourceCodeStart":734,"sourceCodeEnd":770,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/streams.py#L734-L770","documentation":"ValueError raised by StreamReader.readexactly() when n is negative. The function's contract is to return exactly n bytes (or raise IncompleteReadError), which is meaningless for negative n, so it fails fast before checking the buffer.","triggerScenarios":"await reader.readexactly(n) where n is a computed length that went negative: size arithmetic like remaining - chunk with unsigned underflow in concept, a parsed header length field with a negative value, or -1 used as a sentinel reaching this call.","commonSituations":"Protocol parsers reading a length prefix that was validated nowhere; slicing/subtraction mistakes in framing loops; passing a 'read all' sentinel (-1) from a different API into readexactly.","solutions":["Validate n >= 0 before the call and fail with a protocol-framing error if the peer sent a bogus length","Check your size arithmetic: clamp or assert on intermediate values in the framing loop early (fail fast at parse time)","Use reader.read(n) for variable-length reads and readexactly only with a validated non-negative count"],"exampleFix":"// before\npayload = await reader.readexactly(declared_len - header_size)  # can go negative\n\n// after\nn = declared_len - header_size\nif n < 0:\n    raise ProtocolError(f'bad length: {declared_len}')\npayload = await reader.readexactly(n)","handlingStrategy":"validation","validationCode":"def checked_read_size(n: int) -> int:\n    if not isinstance(n, int) or n < 0:\n        raise ValueError(f'readexactly size must be >= 0, got {n!r}')\n    return n","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Validate length prefixes from the wire against protocol bounds before framing reads","Add asserts in framing arithmetic so underflow fails at the source line"],"tags":["asyncio","streams","validation","protocol-parsing"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}