can1357/oh-my-pi · error · RpcError

RPC chunk sequence mismatch

Error message

RPC chunk sequence mismatch

What it means

A chunk arrived whose chunkId, count, byteLength, or index does not match the currently pending reassembly state. Each field must stay constant across the sequence and index must arrive in strict order; any deviation means two sequences interleaved, a sequence restarted, or a chunk was duplicated/reordered. The library rejects it to protect the integrity of the frame being reassembled.

Source

Thrown at python/omp-rpc/src/omp_rpc/client.py:197

        except (binascii.Error, ValueError) as exc:
            raise RpcError("Invalid RPC chunk data") from exc
        if base64.b64encode(chunk).decode("ascii") != data:
            raise RpcError("Invalid RPC chunk data")
        if len(chunk) > _RPC_CHUNK_PAYLOAD_BYTES:
            raise RpcError("RPC chunk payload exceeds the transport limit")

        if self._pending is None:
            if index != 0:
                raise RpcError("RPC chunk sequence must start at index 0")
            self._pending = _PendingRpcChunks(chunk_id, count, byte_length)
        pending = self._pending
        if (
            pending.chunk_id != chunk_id
            or pending.count != count
            or pending.byte_length != byte_length
            or pending.next_index != index
        ):
            raise RpcError("RPC chunk sequence mismatch")
        pending.chunks.append(chunk)
        pending.received_bytes += len(chunk)
        pending.next_index += 1
        if pending.received_bytes > pending.byte_length:
            raise RpcError("RPC chunk sequence exceeds its declared length")
        if pending.next_index < pending.count:
            return None
        if pending.received_bytes != pending.byte_length:
            raise RpcError("RPC chunk sequence length mismatch")

        self._pending = None
        try:
            decoded = b"".join(pending.chunks).decode("utf-8")
            frame = json.loads(decoded)
        except (UnicodeDecodeError, json.JSONDecodeError) as exc:
            raise RpcError("Failed to decode reassembled RPC frame") from exc
        if not isinstance(frame, dict):
            raise RpcError("RPC frame must be a JSON object")

View on GitHub (pinned to 9690622007)

Solutions

  1. Serialize chunked messages: send all chunks of one sequence back-to-back; never interleave two sequences on one client.
  2. Make the sender deterministic: same chunkId, count, and byteLength for every chunk of a sequence.
  3. Guard against duplicate delivery by tracking chunkIds already completed and dropping re-sent sequences.
  4. Ensure byteLength is computed once from the full payload bytes (len(payload.encode('utf-8'))), not per chunk.
  5. On mismatch, abort and request the sender retransmit the complete sequence.

Example fix

// before
# interleaving two messages
send_chunks(msg_a)
for i, c in enumerate(msg_b_chunks):
    client.push(c)  # pushed before msg_a finished -> mismatch

// after
finish_sequence(msg_a)          # push all msg_a chunks, await result
for c in msg_b_chunks:
    client.push(c)              # then send msg_b contiguously
Defensive patterns

Strategy: validation

Validate before calling

def assert_consistent(chunks: list[dict]) -> bool:
    first = chunks[0]
    key = ("chunkId", "count", "byteLength")
    return all(
        all(c.get(k) == first.get(k) for k in key) and c.get("index") == i
        for i, c in enumerate(chunks)
    )

Type guard

def matches_pending(frame: dict, pending_chunk_id: str) -> bool:
    return isinstance(frame, dict) and frame.get("chunkId") == pending_chunk_id

Try / catch

try:
    client.push(frame)
except RpcError as exc:
    if str(exc) == "RPC chunk sequence mismatch":
        logger.error("sequence mismatch: got chunkId=%s index=%s", frame.get("chunkId"), frame.get("index"))
        client.reset_pending()  # or drop client state
        request_retransmit(frame.get("chunkId"))
    else:
        raise

Prevention

When it happens

Trigger: push() of an rpc_chunk whose chunkId differs from the pending sequence's id, whose count or byteLength differs from the values declared by chunk 0, or whose index != pending.next_index (duplicate, skipped, or reordered chunk).

Common situations: Two large messages sent concurrently with interleaved chunks over one channel; a sender retrying from index 0 after a failure without resetting the client state; duplicated frames from an at-least-once queue; sender computing byteLength differently per chunk (e.g. UTF-16 length vs byte length).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/cf0210cde122d472. Report an issue: GitHub.