{"record":{"id":"04ef12db640223f6","repo":"schollz/croc","slug":"received-a-file-chunk-outside-the-advertised-file","errorCode":null,"errorMessage":"Received a file chunk outside the advertised file size","messagePattern":"Received a file chunk outside the advertised file size","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":624,"sourceCode":"  private fail(error: Error) {\n    this.failure ??= error;\n    this.active?.reject(error);\n    this.active = undefined;\n  }\n\n  private accept(position: number, bytes: Uint8Array) {\n    const active = this.active;\n    if (!active) throw new Error(\"Received file data before it was requested\");\n    active.queue = active.queue.then(async () => {\n      if (active.received.has(position)) throw new Error(\"Received a duplicate file chunk\");\n      if (\n        position < 0 ||\n        position % CHUNK_SIZE !== 0 ||\n        bytes.byteLength === 0 ||\n        bytes.byteLength > CHUNK_SIZE ||\n        position + bytes.byteLength > active.file.size\n      ) {\n        throw new Error(\"Received a file chunk outside the advertised file size\");\n      }\n      active.received.add(position);\n      await active.sink.writeAt(position, bytes);\n      active.bytes += bytes.byteLength;\n      active.progress(active.bytes);\n      if (active.bytes === active.file.size) {\n        this.active = undefined;\n        active.resolve();\n      } else if (active.bytes > active.file.size) {\n        throw new Error(\"Received more data than the advertised file size\");\n      }\n    });\n    active.queue.catch((error) => {\n      if (this.active === active) this.active = undefined;\n      active.reject(error instanceof Error ? error : new Error(String(error)));\n    });\n    return active.queue;\n  }","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L606-L642","documentation":"Thrown inside DataReceiver's chunk validation when a chunk fails structural checks: negative position, position not aligned to the 32 KiB CHUNK_SIZE grid, zero-length payload, payload larger than CHUNK_SIZE, or a position+length that exceeds the advertised file size from the sender's fileinfo. These invariants let the receiver write chunks directly at offsets without reassembly logic, so any violation aborts the transfer.","triggerScenarios":"Sender uses a different chunk size than 32768 (e.g. croc CLI default 1024*1024 or a changed constant); position encoding mismatch (endian or width); sender's advertised file size (s field) smaller than actual streamed bytes; file truncated on the sender side after prepareFiles hashed it; corruption that alters position or length bytes.","commonSituations":"CHUNK_SIZE constant changed on one peer but not the other; interoperating with the croc CLI whose chunking differs; sender's File object changes on disk between hashing (prepareFiles) and streaming (sendFileData) so slice() returns fewer/misaligned bytes; version drift in the framing format.","solutions":["Keep CHUNK_SIZE identical on both peers — it is a wire-format constant, not a tunable","Ensure files are not modified between prepareFiles and sendFileData; re-run prepareFiles if the underlying File changes","Confirm the advertised size in senderInfo (s: file.size) matches what sendFileData actually slices","When interoperating with croc CLI, verify its chunk size and framing match this client before mixing implementations"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before sending, assert chunk framing invariants the receiver will enforce\nconst CHUNK_SIZE = 32 * 1024;\nfunction assertChunkOk(position: number, len: number, size: number) {\n  if (position < 0 || position % CHUNK_SIZE !== 0 || len === 0 || len > CHUNK_SIZE || position + len > size) {\n    throw new Error(\"Chunk framing violated before send\");\n  }\n}","typeGuard":"function isAlignedPosition(position: number): boolean {\n  return Number.isSafeInteger(position) && position >= 0 && position % (32 * 1024) === 0;\n}","tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Received a file chunk outside the advertised file size\") {\n    // framing/size mismatch between peers: restart transfer after aligning versions\n  }\n  throw e;\n}","preventionTips":["Treat CHUNK_SIZE (32 KiB) as a wire constant — never change it on one peer only","Ensure files are immutable between prepareFiles and sendFileData (snapshot files that may change)","Verify the advertised size equals the streamed bytes in integration tests"],"tags":["data-integrity","receiver","validation","chunking"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}