.zdelta Sidecars
Rebuild missing regions from bsdiff patches instead of fetching them.
A zsync transfer recovers a target by finding blocks it already holds. Where the residue is absent, a range request closes the gap cheaply. Where the residue differs from what is already held rather than being absent, block matching still has to move all of it.
A .zdelta sidecar carries bsdiff patches for those regions, so the transfer
pays for the difference instead.
The sidecar is additive. The .zsync control file is complete on its own, and
a client that ignores the sidecar — including every other zsync
implementation — still works.
Generating one
zyncmake image.bin -u https://example.com/image.bin --delta image.bin.oldimage.zsync: 977 blocks of 2048 bytes, 7014 bytes of control file
image.bin.old: 1 regions differenced against image.bin
image.zdelta: 19861 bytes of sidecar--delta may be repeated, so a client holding any one of several earlier
versions can benefit.
On a 2 MB target with 200 KB rewritten in place, the same transfer fetches 204800 bytes without a sidecar and nothing at all with one:
fetched 204800 bytes in 1 requests; 0 bytes rebuilt from the sidecar
fetched 0 bytes in 0 requests; 204800 bytes rebuilt from the sidecarFormat
A .zdelta is a zstd-compressed tar holding a meta.json descriptor plus one
bsdiff patch per region — the container shape that
zbsdiff's zdelta feature defines.
{
"format": "zsidecar.v1",
"target_sha1": "…",
"target_length": 2000000,
"regions": [
{
"from_block": 244,
"to_block": 344,
"entry": "region-000000000244-000000000344.bsdiff"
}
]
}zbsdiff defines the container and carries meta.json without interpreting
it, so the descriptor schema belongs to whoever writes it. zsidecar.v1 is
this project's; other projects reuse the same container under their own ids —
Zoi writes zoidelta.v1 for its package and registry deltas. A reader that
checked only the container would otherwise accept the wrong schema.
target_sha1 and target_length say which target the patches rebuild. A
sidecar describing anything else is refused outright: applying it would produce
a file that is not the one asked for.
Regions are keyed by block span
Each region names an exact [from_block, to_block) span, and a patch applies
only to that span. An approximate match is not enough — a patch built for a
wider or narrower span describes different bytes, and stitching the wrong span
in is precisely the corruption the checksums exist to prevent.
The span also has to be the one the client actually needs. Both sides run the same matcher over the same control file and the same base, so the missing blocks are the same set on each.
It is never authoritative
A sidecar is a hint, not a source of truth. Before a rebuilt region is accepted it is checked against the control file's own block checksums, and a patch is discarded if it fails to apply or produces anything other than the right bytes. The region is then fetched normally.
A wrong or hostile sidecar therefore costs bandwidth rather than correctness.
Region size limit
A bsdiff patch is produced from two buffers and applied to a third, so a region
has to fit in memory three times over. Past --max-region the sidecar declines
to carry the region, and the client fetches it as usual:
zyncmake image.bin --delta image.bin.old --max-region 8388608The default is 64 MiB. A region that is too large is left out entirely rather than truncated — a partial patch would rebuild part of a region and leave the rest as though it had been fetched.
The base must be available
A patch describes a difference, so applying one needs the base's own bytes.
Those are in the local copy passed with -i, not in the assembly's temporary
file, which by construction holds only the blocks that were recovered and
nothing at all where the residue is.
That is why supplying -i matters even when the sidecar covers everything the
sidecar covers.
In the library
use std::path::Path;
use zync::delta::{self, DeltaIndex};
fn build(
control: &zync::ControlFile,
base: &Path,
target: &Path,
) -> Result<(), zync::DeltaError> {
let regions = delta::build(control, base, target, u64::MAX)?;
let index =
DeltaIndex::new(control.sha1.clone(), Some(control.length), regions);
index.write(Path::new("image.zdelta"))
}Read one back and hand it to the assembly:
use std::path::Path;
use zync::{Assembly, DeltaIndex};
fn attach(assembly: Assembly) -> Result<Assembly, zync::DeltaError> {
let sidecar = DeltaIndex::read(Path::new("image.zdelta"))?;
Ok(assembly.with_sidecar(sidecar))
}DeltaIndex::describes reports whether a sidecar matches a control file, and
patch_for returns the patch covering an exact block span if there is one.
See the format reference for what the sidecar rides alongside.
2026 © All Rights Reserved.
- All the content is available under CC BY-SA 4.0, expect where otherwise stated.
- Source code is available on GitLab, licensed under Apache 2.0.
Last updated on
