{"record":{"id":"5e1db8aacc4c420a","repo":"can1357/oh-my-pi","slug":"rpc-chunk-sequence-exceeds-its-declared-length","errorCode":null,"errorMessage":"RPC chunk sequence exceeds its declared length","messagePattern":"RPC chunk sequence exceeds its declared length","errorType":"exception","errorClass":"RpcError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/client.py","lineNumber":202,"sourceCode":"            raise RpcError(\"RPC chunk payload exceeds the transport limit\")\n\n        if self._pending is None:\n            if index != 0:\n                raise RpcError(\"RPC chunk sequence must start at index 0\")\n            self._pending = _PendingRpcChunks(chunk_id, count, byte_length)\n        pending = self._pending\n        if (\n            pending.chunk_id != chunk_id\n            or pending.count != count\n            or pending.byte_length != byte_length\n            or pending.next_index != index\n        ):\n            raise RpcError(\"RPC chunk sequence mismatch\")\n        pending.chunks.append(chunk)\n        pending.received_bytes += len(chunk)\n        pending.next_index += 1\n        if pending.received_bytes > pending.byte_length:\n            raise RpcError(\"RPC chunk sequence exceeds its declared length\")\n        if pending.next_index < pending.count:\n            return None\n        if pending.received_bytes != pending.byte_length:\n            raise RpcError(\"RPC chunk sequence length mismatch\")\n\n        self._pending = None\n        try:\n            decoded = b\"\".join(pending.chunks).decode(\"utf-8\")\n            frame = json.loads(decoded)\n        except (UnicodeDecodeError, json.JSONDecodeError) as exc:\n            raise RpcError(\"Failed to decode reassembled RPC frame\") from exc\n        if not isinstance(frame, dict):\n            raise RpcError(\"RPC frame must be a JSON object\")\n        return cast(JsonObject, frame)\n\n\ndef _process_group_id(process: subprocess.Popen[Any]) -> int | None:\n    \"\"\"Process-group id of `process`, or `None` when groups are unavailable.","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/client.py#L184-L220","documentation":"After appending a valid, in-order chunk, the accumulated decoded bytes exceed the byteLength declared by the first chunk of the sequence. The sender's declared total does not bound the actual payload, so the reassembled frame can never be trusted. The library fails fast instead of silently truncating or accepting a mismatched payload.","triggerScenarios":"push() of a chunk that pushes pending.received_bytes past pending.byte_length — typically a sender that miscounted the payload size (e.g. chunked by characters of a str instead of UTF-8 bytes) or appended extra chunks.","commonSituations":"Sender computed byteLength with len(string) but payloads contain multi-byte UTF-8 characters; sender added a terminating extra chunk; chunk boundaries computed on the base64 string rather than decoded bytes; payload mutated between the byteLength computation and chunking.","solutions":["Compute byteLength as len(payload.encode('utf-8')) — the exact decoded byte count — before chunking.","Chunk on encoded bytes, not str characters: split payload.encode('utf-8') into _RPC_CHUNK_PAYLOAD_BYTES slices.","Verify sum(len(chunk) for chunk in chunks) == byteLength on the sender before transmitting.","If the payload can change, recompute chunks and byteLength together atomically."],"exampleFix":"// before\npayload = \"héllo wörld\"\ndeclared = len(payload)  # char count, not bytes\nsend_chunks(payload, byteLength=declared)\n\n// after\npayload_bytes = payload.encode(\"utf-8\")\nsend_chunks(payload_bytes, byteLength=len(payload_bytes))","handlingStrategy":"validation","validationCode":"def prepare_sequence(payload: bytes, chunk_size: int) -> tuple[list[bytes], int]:\n    chunks = [payload[i:i + chunk_size] for i in range(0, len(payload), chunk_size)]\n    byte_length = len(payload)\n    assert sum(len(c) for c in chunks) == byte_length\n    return chunks, byte_length","typeGuard":"def declared_size_matches(chunks: list[bytes], byte_length: int) -> bool:\n    return sum(len(c) for c in chunks) == byte_length","tryCatchPattern":"try:\n    client.push(frame)\nexcept RpcError as exc:\n    if str(exc) == \"RPC chunk sequence exceeds its declared length\":\n        logger.error(\"sender byteLength=%s too small; aborting sequence\", pending_declared)\n        abort_and_request_resend()\n    else:\n        raise","preventionTips":["Always derive byteLength from len(payload.encode('utf-8')) (or len of the bytes object) right before chunking.","Split the encoded bytes, not the str, so multi-byte characters never straddle chunks incorrectly.","Add a sender-side assertion comparing sum of chunk sizes to the declared byteLength."],"tags":["rpc","chunking","encoding","limits"],"backgroundTag":"chunk-sequence-length-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}