hibp_sync_client/lib.rs
1//! Syncs a local HIBP sha1t48 dataset from an
2//! [hibp-bin-fetch](https://crates.io/crates/hibp-bin-fetch) serve instance, with
3//! support for full syncs, delta syncs, and crash-safe resumption.
4//!
5//! This is the client counterpart to `hibp-bin-fetch serve`. It connects to a running
6//! serve instance and downloads whichever prefix files have changed since the last sync,
7//! or all of them on first run. The resulting dataset is compatible with
8//! [hibp-verifier](https://crates.io/crates/hibp-verifier) for sub-microsecond password
9//! breach checking.
10//!
11//! # Sync Modes
12//!
13//! ## Full Sync
14//!
15//! On first run (no local dataset yet), every prefix file is fetched from the server.
16//! The server divides the full dataset into `--segments` chunks and the client fetches
17//! them sequentially, decompressing each segment and writing the prefix files to a staging
18//! directory before atomically moving them to the data directory.
19//!
20//! ## Delta Sync
21//!
22//! After each successful sync the client writes the server's `last_updated` timestamp
23//! to a local state file. This timestamp acts as a version identifier. On the next run,
24//! the client asks the server for its `prev_last_updated` - the timestamp of the cycle
25//! immediately before the current one. If that matches the locally stored timestamp, the
26//! client is exactly one cycle behind and only the changed prefixes need to be fetched.
27//!
28//! If the timestamps do not match (e.g. the client has been offline for multiple nightly
29//! cycles and the server has since moved on), delta sync is not possible and the client
30//! falls back to a full sync automatically.
31//!
32//! # Crash-Safe Design
33//!
34//! All downloads land in a `.staging/` subdirectory of the data directory before being
35//! committed. If the process is interrupted mid-download, the next run resumes from where
36//! it left off - completed segments are not re-fetched. If the server's data has changed
37//! between attempts, the stale staging directory is discarded and a fresh sync starts.
38//!
39//! # Usage
40//!
41//! First, ensure an `hibp-bin-fetch serve` instance is running and accessible.
42//!
43//! ## CLI
44//!
45//! ```sh
46//! hibp-sync-client --server-url http://192.168.1.10:8765 --data-dir ./hibp-data
47//! ```
48//!
49//! With finer resume granularity (default 16 segments):
50//!
51//! ```sh
52//! hibp-sync-client --server-url http://192.168.1.10:8765 --data-dir ./hibp-data --segments 32
53//! ```
54//!
55//! ## Library
56//!
57//! ```rust,ignore
58//! use hibp_sync_client::sync::{Config, Outcome, sync};
59//! use std::path::PathBuf;
60//!
61//! let config = Config {
62//! server_url: "http://192.168.1.10:8765".parse().unwrap(),
63//! data_dir: PathBuf::from("./hibp-data"),
64//! segments: 16,
65//! };
66//!
67//! match sync(&config).await? {
68//! Outcome::UpToDate => println!("already up to date"),
69//! Outcome::DeltaSync { changed_count } => println!("{changed_count} files updated"),
70//! Outcome::FullSync { file_count } => println!("{file_count} files written"),
71//! }
72//! ```
73
74pub mod client;
75pub mod error;
76pub mod sync;
77pub mod wire;