Skip to content

feat(support): deflate-compress support bundles (#3218)#3691

Merged
ten9876 merged 1 commit into
mainfrom
feat/support-bundle-deflate
Jun 21, 2026
Merged

feat(support): deflate-compress support bundles (#3218)#3691
ten9876 merged 1 commit into
mainfrom
feat/support-bundle-deflate

Conversation

@ten9876

@ten9876 ten9876 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #3218. Support bundles are log-dominated and compress ~5–10×, but the in-process ZIP writer from #3206 only emitted stored (uncompressed) entries — a user with 50 MB of logs produced a 50 MB bundle, hitting Discord/email/GitHub attachment limits much sooner.

Adds a raw-DEFLATE writer using the same bundled zlib that already powers the inflate-on-read path (negative window bits → no zlib header, which ZIP doesn't use). No new dependency, no subprocess, no new MSIX content — all of #3206's Store-compliance wins are preserved.

Changes

  • ZipArchive: writeStoredZip and the new writeDeflatedZip now share one writeZipImpl(entries, compress) — method field + independent compressed/uncompressed sizes; CRC is always over the uncompressed data.
  • SupportBundle::writeSupportBundleZipwriteDeflatedZip. ProfileTransfer stays stored (tiny payloads).
  • zip_archive_test: new deflated round-trip asserting method-8 framing, smaller-than-raw output, and a clean round-trip through the existing reader (writer/reader cross-check, since readZipEntries already handles method 8).

Why it's not RFC-gated

Internal storage-format change with no UX/behavior change (bundles are still ZIPs, just smaller); the file is never inside the MSIX so WACK's archive rules don't apply.

Test plan

  • zip_archive_test passes (stored + deflated round-trips, method byte, size check)
  • Full app builds clean

🤖 Generated with Claude Code

Support bundles are log-dominated and compress ~5-10x, but #3206's in-process
ZIP writer only did stored (uncompressed) entries — a user with 50 MB of logs
got a 50 MB bundle, blowing past Discord/email/GitHub attachment limits.

Add a raw-DEFLATE writer using the same bundled zlib that already powers the
inflate-on-read path (negative window bits, no zlib header) — no new dependency,
no subprocess, no new MSIX content, so all of #3206's Store-compliance wins are
preserved. writeStoredZip/writeDeflatedZip now share one writeZipImpl (method +
independent compressed/uncompressed sizes; CRC always over the uncompressed
data). SupportBundle switches to writeDeflatedZip; ProfileTransfer stays stored.

Test: zip_archive_test gains a deflated round-trip that asserts method-8 framing,
smaller-than-raw output, and a clean round-trip through the existing reader
(the implicit writer/reader cross-check).

Closes #3218.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ten9876 ten9876 requested review from a team as code owners June 20, 2026 15:57

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work, @ten9876 — this is a tidy, well-scoped change and the approach is exactly right. Reusing the bundled zlib with negative window bits (-MAX_WBITS) keeps the writer's raw-DEFLATE framing in lockstep with the existing inflateInit2(-MAX_WBITS) read path, so no new dependency and no MSIX impact. The shared writeZipImpl(entries, compress) cleanly factors the stored/deflated split.

Things I checked and confirmed correct:

  • CRC is over uncompressed data, while local + central headers carry compressed (payload) and uncompressed (raw) sizes independently — matches the ZIP spec and what readZipEntries expects (it inflates compressedSize bytes, then CRCs the inflated result).
  • Bounds: raw, payload, name, and out sizes are all checked against their quint32/quint16 limits before casting. rawDeflate checks deflateInit2/Z_STREAM_END return codes and deflateEnds on every path.
  • Method byte = 8 in both local and central headers; version-needed stays 20 (2.0), correct for deflate.
  • Empty-file edge case is covered by the test (empty.txt round-trips), which is the one spot raw-deflate framing can be subtle — good that the test asserts it through the real reader rather than just the writer.
  • The writer/reader cross-check test (round-trip through readZipEntries) is the right way to test this, and all 6 CI checks are green.

One optional thought (not blocking): writeDeflatedZip always emits method 8 per entry, so a tiny or already-incompressible file can come out slightly larger than stored (deflate overhead). For log-dominated support bundles this is irrelevant, and the output is always a valid ZIP regardless — just noting that a per-entry min(stored, deflated) choice would be the fully general form if this writer ever gets reused for less compressible payloads. Fine to leave as-is for this use case.

LGTM. Thanks for preserving all of #3206's Store-compliance properties.


🤖 aethersdr-agent · cost: $2.4515 · model: claude-opus-4-8

@ten9876 ten9876 merged commit b8d97e2 into main Jun 21, 2026
6 checks passed
@ten9876 ten9876 deleted the feat/support-bundle-deflate branch June 21, 2026 03:23
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.

Add deflate compression to writeStoredZip for support bundles

1 participant