Skip to content

docs(download): document DownloadWriter and the writer-truncation guarantee - #471

Merged
jlucaso1 merged 2 commits into
mainfrom
claude/nifty-bohr-alfhsy
Jul 30, 2026
Merged

jlucaso1 merged 2 commits into
mainfrom
claude/nifty-bohr-alfhsy

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Follows up on oxidezap/whatsapp-rust#1197, which fixed a bug where a failed CDN host could leave stale, unverified plaintext past the end of a successful download_to_writer retry.

What changed upstream

download_to_writer, download_from_params_to_writer, and MediaDownloader::download_to_writer now take W: DownloadWriter (a new trait: Write + Seek plus truncate) instead of plain W: Write + Seek. This is a breaking API change. Every attempt now starts by truncating the writer to empty rather than only rewinding it, so:

  • On success, the writer holds exactly the decrypted media — no leftover content from the caller, no tail from a host that streamed plaintext before failing its MAC.
  • On failure (every host exhausted), the writer is emptied too, on a best-effort basis.

std::fs::File, std::io::Cursor<Vec<u8>>, std::io::Cursor<&mut Vec<u8>>, std::io::BufWriter<W>, and &mut W all implement DownloadWriter already; a custom writer needs one additional method.

Doc updates

  • api/download.mdx
    • Updated the download_to_writer, download_from_params_to_writer, and MediaDownloader::download_to_writer signatures and param/response fields to DownloadWriter.
    • Rewrote the retry-behavior paragraph to describe truncate-then-rewind instead of rewind-only.
    • Added a new ## DownloadWriter Trait section: the trait definition, why Write + Seek wasn't enough, the built-in implementations, and a short example implementing it for a custom wrapper type — with a Breaking change (as of PR #1197) callout in the same style as the existing MediaConnection → MediaRoute note.
  • guides/media-handling.mdx
    • Updated the "Streaming with Progress" ProgressWriter example to implement DownloadWriter (it would no longer compile against the new bound otherwise) and added a lead-in sentence pointing to the new reference section.
    • Updated the "Automatic retry and failover" paragraph to describe the new empty-then-rewind + best-effort-cleanup-on-failure behavior.

Per the usual policy, no changelog entry was added — that's left for a human to write.


Generated by Claude Code


Summary by cubic

Documented the new DownloadWriter trait and the truncate-then-rewind behavior for streaming downloads. Clarifies that writers contain exactly the decrypted media on success and are emptied on failure on a best-effort basis; updates API signatures, refines the append-mode explanation (truncate vs seek + final seek(0)), and fixes the ProgressWriter example (counter reset, adds File import).

  • Migration
    • These now require W: DownloadWriter: download_to_writer, download_from_params_to_writer, MediaDownloader::download_to_writer.
    • No changes if using std::fs::File, Cursor<Vec<u8>>, Cursor<&mut Vec<u8>>, BufWriter<W>, or &mut W.
    • Custom/wrapper writers must implement DownloadWriter::truncate; the Progress writer guide shows a one-line delegate.

Written for commit 5461a0d. Summary will update on new commits.

Summary by CodeRabbit

  • Documentation
    • Clarified streaming download retry behavior, including writer reset and failure handling.
    • Added documentation for the new DownloadWriter requirement and its truncation behavior.
    • Updated media-handling guidance and examples for progress-aware streaming downloads.
    • Documented that successful downloads contain only verified media, while failed retries leave the writer empty on a best-effort basis.

…rantee from whatsapp-rust#1197

download_to_writer, download_from_params_to_writer, and MediaDownloader::download_to_writer
now require W: DownloadWriter (Write + Seek + truncate) instead of plain Write + Seek, so a
failed host's plaintext can no longer leave a stale tail behind a shorter successful retry.
Documents the new trait, its built-in impls, the updated writer contract (exactly the media
on success, empty on failure), and updates the ProgressWriter example to implement it.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jlucaso1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9723e2de-2176-4464-b0a0-9c362ee59d07

📥 Commits

Reviewing files that changed from the base of the PR and between 8f51852 and 5461a0d.

📒 Files selected for processing (2)
  • api/download.mdx
  • guides/media-handling.mdx
📝 Walkthrough

Walkthrough

Documents the new DownloadWriter requirement for streaming downloads, including truncation-based retry handling, failure cleanup, updated API signatures, and progress-wrapper integration.

Changes

Download writer contract

Layer / File(s) Summary
DownloadWriter contract and guarantees
api/download.mdx
Documents the DownloadWriter trait, required truncate method, retry truncation, rewind behavior, and exact output or best-effort empty failure states.
Download API signature updates
api/download.mdx
Updates download function bounds and writer response documentation to require DownloadWriter.
Progress wrapper and retry guide
guides/media-handling.mdx
Updates ProgressWriter to delegate truncate and describes writer reset behavior during retries and final failure.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A bunny trims the writer clean,
Then hops it back to zero’s sheen.
Each retry starts with bytes anew,
No stale tail can tumble through.
“DownloadWriter!” the rabbit sings,
With tidy streams and helpful springs.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the doc update around DownloadWriter and the truncation guarantee.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mintlify

mintlify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
whatsapp-rust 🟢 Ready View Preview Jul 30, 2026, 1:08 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f518528a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/download.mdx Outdated

---

## DownloadWriter Trait

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Change the heading to sentence case

The repository requires sentence case for headings, but DownloadWriter Trait capitalizes the common noun Trait. Change it to DownloadWriter trait so the new section follows the site convention.

AGENTS.md reference: AGENTS.md:L26-L26

Useful? React with 👍 / 👎.

Comment thread api/download.mdx Outdated
}
```

Media is authenticated by a single MAC over the whole ciphertext, so decryption has necessarily streamed plaintext into the writer by the time a forged body is caught, and a retry against the next host may end up writing fewer bytes than the attempt it replaces. Rewinding with `seek` alone can't remove bytes that are already there — shortening a sink requires a concrete operation (`File::set_len`, `Vec::truncate`) that no `std` trait exposes. `DownloadWriter::truncate` names that operation, which is what lets `download_to_writer` guarantee: **exactly the media on success, empty on failure.**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify the empty-on-failure guarantee

When truncate(0) fails during final best-effort cleanup, a caller with a shared handle can still observe pre-existing or unverified bytes—the note above explicitly acknowledges that cleanup can fail. Calling empty on failure a guarantee therefore encourages consumers to trust state the API does not promise; qualify the failure case as best effort while retaining the exact-on-success guarantee.

Useful? React with 👍 / 👎.

Comment thread guides/media-handling.mdx Outdated
Comment on lines +654 to +655
fn truncate(&mut self, len: u64) -> std::io::Result<()> {
self.inner.truncate(len)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset the progress count when truncating

When a host writes bytes and then fails validation, retry startup calls this truncate(0), but the implementation truncates only the inner writer and leaves total unchanged. The next attempt therefore reports failed-attempt bytes plus current bytes and can finish above the actual file size; synchronize the shared counter with len after successful truncation so the progress example remains correct under automatic failover.

Useful? React with 👍 / 👎.

Comment thread api/download.mdx Outdated

Media is authenticated by a single MAC over the whole ciphertext, so decryption has necessarily streamed plaintext into the writer by the time a forged body is caught, and a retry against the next host may end up writing fewer bytes than the attempt it replaces. Rewinding with `seek` alone can't remove bytes that are already there — shortening a sink requires a concrete operation (`File::set_len`, `Vec::truncate`) that no `std` trait exposes. `DownloadWriter::truncate` names that operation, which is what lets `download_to_writer` guarantee: **exactly the media on success, empty on failure.**

Every attempt begins by truncating the writer to 0 and rewinding it, so only that attempt's own bytes are ever present when it finishes — including on an append-mode `File`, where truncating (not just seeking) is what brings the write position back to the start, since files opened for appending ignore `seek` and always write at the end.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct the append-mode cursor explanation

For an append-mode File, set_len(0) does not move the cursor, and append mode does not make seek a no-op: the explicit rewind changes the current position, while each write is separately forced to the current end of the file. This explanation can mislead custom implementers about which operation satisfies each part of the contract; describe truncation as clearing the file length and seeking as resetting its position.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/download.mdx`:
- Around line 140-142: The DownloadWriter trait documentation should explicitly
describe the separate final rewind performed after a successful download. Update
the trait section near the existing truncate-and-rewind-attempt behavior to
state that successful completion seeks the writer back to position 0, matching
the postcondition documented for the ResponseField “writer”.

In `@guides/media-handling.mdx`:
- Line 622: Rewrite the guidance around download_to_writer in active voice and
address the reader directly, stating that when the reader passes a writer, they
must implement DownloadWriter rather than only Write + Seek. Preserve the
existing reference and one-line delegation guidance.
- Around line 624-627: Add the missing std::fs::File import to the fenced Rust
snippet containing DownloadWriter and the File::create("video.mp4") usage, so
the example compiles without changing its existing behavior.
- Line 682: Update the streaming-download documentation around
“download_to_writer” to describe writer cleanup before each attempt, including
the first, rather than before each retry. Use active voice stating that the
client empties and rewinds the writer, and retain the existing final best-effort
cleanup behavior and DownloadWriter reference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b381bf59-9dfa-4699-ad8d-cb2554d450f1

📥 Commits

Reviewing files that changed from the base of the PR and between 196405a and 8f51852.

📒 Files selected for processing (2)
  • api/download.mdx
  • guides/media-handling.mdx

Comment thread api/download.mdx
Comment thread guides/media-handling.mdx Outdated
Comment thread guides/media-handling.mdx
Comment thread guides/media-handling.mdx Outdated
- Sentence-case the DownloadWriter trait heading.
- Separate truncate's role (clears length) from seek's (resets position)
  in the append-mode File explanation, and note the final seek(0) that
  produces the "seeked back to position 0" postcondition on success.
- Qualify empty-on-failure as best-effort, matching the existing note.
- Fix ProgressWriter's truncate to reset its own byte counter, and add
  the File import the example was missing.
- Rewrite "before each retry — including the first attempt" (contradictory)
  as "before each attempt, including the first", in active voice.
@jlucaso1
jlucaso1 merged commit f6a2d3b into main Jul 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants