The rsync algorithm over binaries, wire-compatible with librsync 2.x.
The receiver, which holds the old version (the basis), computes a signature: a weak rolling checksum and a strong hash for each fixed-size block. The sender, which holds the new version, slides a window over its data, looks each window's weak checksum up in the signature, and confirms candidates with the strong hash. Matching windows become copy commands that reference basis blocks; bytes in between become literal commands. The receiver replays the commands against its basis to rebuild the new version. Only the signature and the delta cross the wire, so the cost of a transfer scales with what changed rather than with the file size.
Example
iex> basis = String.duplicate("the quick brown fox jumps over the lazy dog. ", 20)
iex> new = String.replace(basis, "lazy", "sleepy", global: false)
iex> signature = Rexd.signature(basis, block_len: 64)
iex> wire = signature |> Rexd.Signature.encode() |> IO.iodata_to_binary()
iex> {:ok, received} = Rexd.Signature.decode(wire)
iex> delta = Rexd.delta(received, new)
iex> Rexd.patch(basis, delta) == {:ok, new}
trueModules
Rexd.Signature,Rexd.Delta- the data structures and their librsync wire formats.Rexd.Stream- the same operations over enumerables of binaries, in bounded memory.Rexd.InPlace- rebuilding the new version inside the basis storage.Rexd.Blake2b- BLAKE2b-256 in Elixir, the default strong hash, usable on its own.
Summary
Functions
Computes the delta that turns the basis described by signature into new.
Like delta/3, also returning Rexd.Delta.Stats for the delta and the
search.
Rebuilds the new binary by applying delta to basis.
The block length rdiff picks for a basis of size bytes: 256 up to
65 536 bytes, otherwise the integer square root of size rounded down to a
multiple of 128 (librsync rs_sig_args).
Computes the signature of basis.
Types
@type patch_error() :: {:copy_out_of_range, non_neg_integer(), non_neg_integer()} | {:output_too_large, non_neg_integer(), non_neg_integer()}
Reasons patch/3 can fail.
Functions
@spec delta(Rexd.Signature.t(), binary(), keyword()) :: Rexd.Delta.t()
Computes the delta that turns the basis described by signature into new.
The signature's weak-checksum index is built on entry when absent; call
Rexd.Signature.build_index/1 once to reuse it across several deltas.
Options
:in_place- whentrue, the delta can also be applied in place withRexd.InPlace.patch/4: copies that would close a dependency cycle are sent as literals instead. Defaultfalse.
@spec delta_with_stats(Rexd.Signature.t(), binary(), keyword()) :: {Rexd.Delta.t(), Rexd.Delta.Stats.t()}
Like delta/3, also returning Rexd.Delta.Stats for the delta and the
search.
iex> basis = String.duplicate("0123456789abcdef", 4)
iex> sig = Rexd.signature(basis, block_len: 16)
iex> {_delta, stats} = Rexd.delta_with_stats(sig, "xyz" <> basis)
iex> {stats.literal_bytes, stats.copy_bytes, stats.copy_commands}
{3, 64, 1}
@spec patch(binary(), Rexd.Delta.t(), keyword()) :: {:ok, binary()} | {:error, patch_error()}
Rebuilds the new binary by applying delta to basis.
iex> basis = "the quick brown fox jumps over the lazy dog"
iex> new = "the quick red fox jumps over the lazy dog"
iex> sig = Rexd.signature(basis, block_len: 8)
iex> Rexd.patch(basis, Rexd.delta(sig, new))
{:ok, "the quick red fox jumps over the lazy dog"}Options
:max_size- the largest output, in bytes, the call may produce; default:infinity. Checked before any output is built. Set it when the delta comes from an untrusted source.
Errors
{:copy_out_of_range, offset, length}- a copy command reaches past the end ofbasis, typically because the delta was computed against a different basis. A delta computed against a different basis of sufficient length patches without error and produces wrong output; detecting that requires a checksum of the expected result, which the librsync format does not carry.{:output_too_large, size, max_size}- the output would exceed:max_size.
Raises ArgumentError on invalid options and FunctionClauseError on a
malformed Rexd.Delta struct (a delta returned by delta/2 or
Rexd.Delta.decode/1 is always well-formed).
@spec recommended_block_len(non_neg_integer()) :: pos_integer()
The block length rdiff picks for a basis of size bytes: 256 up to
65 536 bytes, otherwise the integer square root of size rounded down to a
multiple of 128 (librsync rs_sig_args).
iex> Rexd.recommended_block_len(1_000)
256
iex> Rexd.recommended_block_len(100_000_000)
9984
@spec signature(binary(), keyword()) :: Rexd.Signature.t()
Computes the signature of basis.
Options
:block_len- bytes per block, default2048(librsync's default for an input of unknown size). Seerecommended_block_len/1.:strong_sum_len- bytes of the strong hash kept per block:1..32for BLAKE2b,1..16for MD4; defaults to the maximum.:weak- rolling checksum,:rabinkarp(default) or:rollsum.:strong- strong hash,:blake2(default) or:md4.
See Rexd.Signature for the four resulting librsync signature types.
Raises ArgumentError on invalid options.