Skip to main content

tsaur_core/
transfer.rs

1//! Direct piece exchange between two T-saur instances (prototype): a tiny request/response
2//! protocol over TCP with manually supplied peer addresses. No discovery, no NAT traversal, no
3//! tracker, no account. The server only serves bytes of the volumes it was started with; the
4//! client verifies every descriptor and every piece against what it expects
5//! (`docs/design/VOLUME-TRUST.md`) and writes volumes with the offline format, so `join`,
6//! `inspect` and `repair` are unchanged.
7//!
8//! Wire format ("TSXP/1"), one request per connection, an ASCII line of at most 256 bytes:
9//!
10//! ```text
11//! TSXP/1 SETS                                 -> OK <len>\n + CBOR [[set id hex, archive name, archive size], ...]
12//! TSXP/1 DESCRIPTOR <set id hex>              -> OK <len>\n + the CBOR descriptor
13//! TSXP/1 HAVE <set id hex>                    -> OK <len>\n + CBOR [volume index, ...] (complete, verified volumes)
14//! TSXP/1 PIECE <set id hex> <volume> <stripe> -> OK <len>\n + the piece bytes
15//! anything else / unknown / too busy          -> ERR <code> <message>\n
16//! ```
17//!
18//! The transport is plain TCP by default: it authenticates nothing and hides nothing, and
19//! integrity comes only from the hashes the receiver already expects. With TLS (`crate::tls`)
20//! the same requests run inside TLS 1.3 between two self-signed certificates that each side
21//! pins by fingerprint: the client accepts only the server whose certificate hashes to the
22//! expected fingerprint, and the server accepts only clients on its allow list (mutual
23//! authentication) or, when so configured, any client. No certificate authority, account or
24//! service is involved; fingerprints travel through the same trusted channel as the set id
25//! (`docs/design/VOLUME-TRUST.md` §6). A mismatch ends the handshake before any request is
26//! read; a plain client against a TLS server (or the reverse) fails without a response.
27//!
28//! # Limits (all explicit, all enforced)
29//!
30//! | What | Limit | Where |
31//! |---|---|---|
32//! | request line | 256 bytes, ASCII, newline-terminated | server and client |
33//! | descriptor response | `volumes::MAX_DESCRIPTOR` (64 MiB) | client (`request` cap) and `VolumeSet::validate` |
34//! | pieces per set | `volumes::MAX_PIECES` | `VolumeSet::validate` |
35//! | `HAVE` / `SETS` responses | 4 KiB / 1 MiB | client |
36//! | piece response | exactly the piece size (64 KiB .. 64 MiB) | client; the server streams it in 1 MiB steps |
37//! | memory | one piece per fetch, one 1 MiB buffer per served connection, plus the descriptor | by construction |
38//! | temporary space | the full size of every wanted volume is reserved (`set_len`) before its first piece; `FetchReport::bytes_reserved` | client |
39//! | concurrent connections | `ServerLimits::max_connections` (64) in total, `max_connections_per_peer` (8) per source address; excess gets `ERR 503` | server |
40//! | request rate | `ServerLimits::max_requests_per_second` (200) new connections per second per source address, twice that in a burst (token bucket); excess gets `ERR 429`; clients back off and retry for about two seconds | server, client |
41//! | waiting | 30 s connect / read / write timeouts on both sides | both |
42//! | connection budget | the request line (and a TLS handshake) must arrive within `timeout`; a response of L bytes must be consumed within max(`timeout`, L / `min_rate`) (`min_rate` 64 KiB/s); a peer that trickles bytes or stops reading is cut off and its slot released | server |
43//! | TLS | the handshake (and the peer's certificate check) completes before any request byte is read; in TLS mode an excess connection is closed without a reply instead of `ERR 503`, so no handshake is spent on it | server |
44//! | bandwidth | `ServerLimits::max_bandwidth` (unlimited by default): one token bucket of bytes shared by every connection, paid in slices of at most 1/8 s of the rate; the time a connection spends throttled is credited to its budget, so the cap never triggers the `min_rate` cut-off | server |
45//! | distinct addresses | `ServerLimits::max_peers` (64): source addresses with an open connection; a further address gets `ERR 503` (a silent close in TLS mode) until one leaves; an address is a resource key, not an identity | server |
46//! | authorization | the handshake admits the union of the global allow list and the per-set lists (`Acl`); a set outside a client's lists is answered like an unknown set (`404`), so a client learns nothing about sets it may not read; a revoked fingerprint (`tls::Revocations`) fails the handshake on either side even when pinned or allowed | server, client |
47//! | local file names | derived from the set id when the descriptor's archive name is not a safe single path component (`VolumeSet::volume_name`) | client, repair |
48//!
49//! Interrupted transfers resume: a volume being fetched lives under `<name>.partial` next to a
50//! `.partial.map` listing the pieces believed received. The map is a hint, never a proof: on
51//! resume every piece it declares complete is read back and hash-checked before it is trusted,
52//! a damaged or truncated partial file loses only the pieces that fail, and an unreadable map
53//! means every position of the partial file is checked from disk.
54
55use crate::chunk;
56use crate::error::{Error, Result};
57use crate::format;
58use crate::tls::{self, Revocations};
59use crate::volumes::{self, VolumeSet, HEADER_LEN, MAX_DESCRIPTOR};
60use rustls::{ClientConfig, ClientConnection, ServerConfig, ServerConnection, StreamOwned};
61use serde::{Deserialize, Serialize};
62use serde_bytes::ByteBuf;
63use std::cell::RefCell;
64use std::collections::{BTreeMap, HashMap, HashSet};
65use std::fs::{File, OpenOptions};
66use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
67use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs};
68use std::path::{Path, PathBuf};
69use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
70use std::sync::{Arc, Mutex};
71use std::time::{Duration, Instant};
72
73pub const PROTOCOL: &str = "TSXP/1";
74pub const MAX_LINE: usize = 256;
75pub const MAX_HAVE_RESPONSE: usize = 4 << 10;
76pub const MAX_SETS_RESPONSE: usize = 1 << 20;
77pub const TIMEOUT: Duration = Duration::from_secs(30);
78const STREAM_CHUNK: usize = 1 << 20;
79
80fn hash32(h: &[u8]) -> [u8; 32] {
81    let mut out = [0u8; 32];
82    out.copy_from_slice(&h[..32]);
83    out
84}
85
86fn parse_hex32(s: &str) -> Option<[u8; 32]> {
87    let v = hex::decode(s).ok()?;
88    (v.len() == 32).then(|| hash32(&v))
89}
90
91/// True when `listen` names a loopback address (the only default the CLI accepts without an
92/// explicit opt-in).
93pub fn is_loopback(listen: &str) -> Result<bool> {
94    let addr = listen.to_socket_addrs().ok().and_then(|mut a| a.next()).ok_or_else(|| Error::Invalid(format!("listen address {listen}")))?;
95    Ok(addr.ip().is_loopback())
96}
97
98// ---------------------------------------------------------------- server
99
100struct Served {
101    set: VolumeSet,
102    descriptor: Vec<u8>,
103    /// Paths of complete, verified volumes by index.
104    volumes: Vec<Option<PathBuf>>,
105}
106
107/// Resource limits of a server; the defaults suit a local network.
108#[derive(Clone, Copy, Debug)]
109pub struct ServerLimits {
110    /// Concurrent connections in total.
111    pub max_connections: usize,
112    /// Concurrent connections per source address.
113    pub max_connections_per_peer: usize,
114    /// Per read/write operation; also the whole budget for receiving the request line (and the
115    /// TLS handshake before it).
116    pub timeout: Duration,
117    /// New connections per second per source address, sustained; a burst of twice as many is
118    /// admitted (token bucket). One request is one connection, so this is also the piece rate
119    /// one address can obtain; a sequential fetch with the adaptive piece sizes stays well below
120    /// the default on a gigabit link (1 MiB pieces are about 120 per second).
121    pub max_requests_per_second: u32,
122    /// Bytes per second a client must at least consume: a response of L bytes has to be read
123    /// within max(`timeout`, L / `min_rate`), after which the connection is closed.
124    pub min_rate: u64,
125    /// Bytes per second the server sends in total, across all connections (`None`: unlimited).
126    /// One token bucket holding one second of the rate, shared by every connection in arrival
127    /// order; the effective rate of one client is the cap divided by the active connections.
128    pub max_bandwidth: Option<u64>,
129    /// Distinct source addresses with at least one open connection; a further address is turned
130    /// away like an excess connection (`503`, silent in TLS mode) until one of them leaves.
131    pub max_peers: usize,
132}
133
134impl Default for ServerLimits {
135    fn default() -> Self {
136        Self { max_connections: 64, max_connections_per_peer: 8, timeout: TIMEOUT, max_requests_per_second: 200, min_rate: 64 << 10, max_bandwidth: None, max_peers: 64 }
137    }
138}
139
140/// Why a connection was not admitted.
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142enum Admit {
143    Ok,
144    Busy,
145    RateLimited,
146}
147
148struct Bucket {
149    tokens: f64,
150    last: Instant,
151}
152
153struct Gate {
154    limits: ServerLimits,
155    total: AtomicUsize,
156    per_ip: Mutex<HashMap<IpAddr, usize>>,
157    buckets: Mutex<HashMap<IpAddr, Bucket>>,
158    /// The global bandwidth bucket (tokens are bytes), present when `max_bandwidth` is set.
159    throttle: Option<Mutex<Bucket>>,
160}
161
162impl Gate {
163    fn new(limits: ServerLimits) -> Gate {
164        let throttle = limits.max_bandwidth.map(|rate| Mutex::new(Bucket { tokens: rate as f64, last: Instant::now() }));
165        Gate { limits, total: AtomicUsize::new(0), per_ip: Mutex::new(HashMap::new()), buckets: Mutex::new(HashMap::new()), throttle }
166    }
167
168    fn enter(&self, ip: IpAddr) -> Admit {
169        let mut per_ip = self.per_ip.lock().unwrap_or_else(|e| e.into_inner());
170        let mine = per_ip.get(&ip).copied().unwrap_or(0);
171        if self.total.load(Ordering::Relaxed) >= self.limits.max_connections || mine >= self.limits.max_connections_per_peer {
172            return Admit::Busy;
173        }
174        // an address that is not present yet needs a free address slot (`per_ip` drops an
175        // address at its last `leave`, so "present" is exactly "has an open connection")
176        if mine == 0 && per_ip.len() >= self.limits.max_peers {
177            return Admit::Busy;
178        }
179        // token bucket per source address: `max_requests_per_second` sustained, twice that in a burst
180        let rate = f64::from(self.limits.max_requests_per_second.max(1));
181        let burst = rate * 2.0;
182        let now = Instant::now();
183        let mut buckets = self.buckets.lock().unwrap_or_else(|e| e.into_inner());
184        if buckets.len() > 4096 {
185            // forget addresses whose bucket would be full again by now (nothing to remember)
186            buckets.retain(|_, b| b.tokens + now.duration_since(b.last).as_secs_f64() * rate < burst);
187        }
188        let b = buckets.entry(ip).or_insert(Bucket { tokens: burst, last: now });
189        b.tokens = (b.tokens + now.duration_since(b.last).as_secs_f64() * rate).min(burst);
190        b.last = now;
191        if b.tokens < 1.0 {
192            return Admit::RateLimited;
193        }
194        b.tokens -= 1.0;
195        per_ip.insert(ip, mine + 1);
196        self.total.fetch_add(1, Ordering::Relaxed);
197        Admit::Ok
198    }
199
200    fn leave(&self, ip: IpAddr) {
201        let mut per_ip = self.per_ip.lock().unwrap_or_else(|e| e.into_inner());
202        if let Some(n) = per_ip.get_mut(&ip) {
203            *n = n.saturating_sub(1);
204            if *n == 0 {
205                per_ip.remove(&ip);
206            }
207        }
208        self.total.fetch_sub(1, Ordering::Relaxed);
209    }
210
211    /// Largest slice one write may take from the bandwidth bucket: at most an eighth of a
212    /// second of the rate, at least 4 KiB, so that no connection waits long for one grant.
213    fn slice_len(&self) -> Option<usize> {
214        self.limits.max_bandwidth.map(|rate| ((rate / 8) as usize).max(4 << 10))
215    }
216
217    /// Take `n` bytes from the shared bandwidth bucket and return how long the caller has to
218    /// wait before sending them (zero when tokens were available). The debt is recorded at once,
219    /// so concurrent writers queue up in arrival order; the caller sleeps outside the lock.
220    fn take_bytes(&self, n: usize) -> Duration {
221        let (Some(bucket), Some(rate)) = (&self.throttle, self.limits.max_bandwidth) else { return Duration::ZERO };
222        let rate = rate.max(1) as f64;
223        let mut b = bucket.lock().unwrap_or_else(|e| e.into_inner());
224        let now = Instant::now();
225        b.tokens = (b.tokens + now.duration_since(b.last).as_secs_f64() * rate).min(rate);
226        b.last = now;
227        b.tokens -= n as f64;
228        if b.tokens >= 0.0 {
229            Duration::ZERO
230        } else {
231            Duration::from_secs_f64(-b.tokens / rate)
232        }
233    }
234}
235
236/// Which client may read which set (`docs/design/VOLUME-TRUST.md` §6.4). The handshake admits
237/// every listed fingerprint; this decides, per request, whether the admitted client may read the
238/// set it asks for. A set outside a client's lists is answered like an unknown set.
239#[derive(Clone, Debug, Default)]
240pub struct Acl {
241    anyone: bool,
242    any_set: HashSet<[u8; 32]>,
243    per_set: HashMap<[u8; 32], HashSet<[u8; 32]>>,
244}
245
246impl Acl {
247    /// Every admitted connection may read every set (plain mode, `--allow-anyone`, and a plain
248    /// global allow list, where the handshake already decided).
249    pub fn everyone() -> Acl {
250        Acl { anyone: true, ..Acl::default() }
251    }
252
253    /// `any_set`: fingerprints that may read every served set; `per_set`: (set id, fingerprint)
254    /// pairs for clients restricted to specific sets.
255    pub fn new(any_set: impl IntoIterator<Item = [u8; 32]>, per_set: impl IntoIterator<Item = ([u8; 32], [u8; 32])>) -> Acl {
256        let mut acl = Acl { anyone: false, any_set: any_set.into_iter().collect(), per_set: HashMap::new() };
257        for (set, fp) in per_set {
258            acl.per_set.entry(set).or_default().insert(fp);
259        }
260        acl
261    }
262
263    /// Every fingerprint the handshake must admit (the union of all lists).
264    pub fn admitted(&self) -> Vec<[u8; 32]> {
265        let mut all: Vec<[u8; 32]> = self.any_set.iter().copied().chain(self.per_set.values().flatten().copied()).collect();
266        all.sort_unstable();
267        all.dedup();
268        all
269    }
270
271    /// Fingerprints that may read only some sets (none of them is in the global list).
272    pub fn restricted(&self) -> Vec<[u8; 32]> {
273        let mut r: Vec<[u8; 32]> = self.per_set.values().flatten().copied().filter(|fp| !self.any_set.contains(fp)).collect();
274        r.sort_unstable();
275        r.dedup();
276        r
277    }
278
279    /// How many identities may read `set`: `None` when everyone admitted may.
280    pub fn readers_of(&self, set: &[u8; 32]) -> Option<usize> {
281        if self.anyone {
282            return None;
283        }
284        Some(self.any_set.len() + self.per_set.get(set).map(|s| s.iter().filter(|fp| !self.any_set.contains(*fp)).count()).unwrap_or(0))
285    }
286
287    pub fn may_read(&self, client: Option<&[u8; 32]>, set: &[u8; 32]) -> bool {
288        self.anyone || client.is_some_and(|c| self.any_set.contains(c) || self.per_set.get(set).is_some_and(|s| s.contains(c)))
289    }
290}
291
292/// A read-only server for the volumes found under `paths`.
293pub struct Server {
294    sets: Arc<HashMap<[u8; 32], Served>>,
295    listener: TcpListener,
296    gate: Arc<Gate>,
297    tls: Option<Arc<ServerConfig>>,
298    acl: Arc<Acl>,
299}
300
301#[derive(Clone, Debug, Serialize)]
302pub struct ServedSet {
303    pub set_id: String,
304    pub archive_name: String,
305    pub archive_size: u64,
306    pub volumes: Vec<usize>,
307}
308
309impl Server {
310    /// Inspect and verify the volumes under `paths`, bind `listen` (for example `127.0.0.1:0`).
311    pub fn bind(paths: &[PathBuf], listen: &str) -> Result<Server> {
312        Self::bind_with(paths, listen, ServerLimits::default())
313    }
314
315    pub fn bind_with(paths: &[PathBuf], listen: &str, limits: ServerLimits) -> Result<Server> {
316        Self::bind_tls(paths, listen, limits, None)
317    }
318
319    /// Like `bind_with`; with `tls` every connection is a TLS session using that configuration
320    /// (see `tls::server_config`: the server's own identity and the client allow list). Every
321    /// admitted client may read every set.
322    pub fn bind_tls(paths: &[PathBuf], listen: &str, limits: ServerLimits, tls: Option<Arc<ServerConfig>>) -> Result<Server> {
323        Self::bind_acl(paths, listen, limits, tls, Acl::everyone())
324    }
325
326    /// Like `bind_tls` with per-request authorization: `acl` decides which admitted client may
327    /// read which set (the TLS configuration must admit `acl.admitted()`; the two are built
328    /// together by the CLI). Without `tls` there is no client identity, so `acl` must be
329    /// `Acl::everyone()` (anything else would refuse every request).
330    pub fn bind_acl(paths: &[PathBuf], listen: &str, limits: ServerLimits, tls: Option<Arc<ServerConfig>>, acl: Acl) -> Result<Server> {
331        if tls.is_none() && !acl.anyone {
332            return Err(Error::Invalid("per-set client lists need TLS: a plain connection carries no client identity".into()));
333        }
334        let statuses = volumes::inspect(paths, true)?;
335        let mut sets = HashMap::new();
336        for st in statuses {
337            let mut id = [0u8; 32];
338            id.copy_from_slice(&hex::decode(&st.set_id).map_err(|_| Error::Corrupt("set id".into()))?);
339            let vols: Vec<Option<PathBuf>> = st.volumes.iter().map(|v| v.as_ref().filter(|f| f.usable() && (f.descriptor_ok || f.header_ok)).map(|f| f.path.clone())).collect();
340            let descriptor = format::cbor_encode(&st.set)?;
341            sets.insert(id, Served { set: st.set, descriptor, volumes: vols });
342        }
343        let listener = TcpListener::bind(listen).map_err(|e| Error::Io(std::io::Error::other(format!("bind {listen}: {e}"))))?;
344        Ok(Server { sets: Arc::new(sets), listener, gate: Arc::new(Gate::new(limits)), tls, acl: Arc::new(acl) })
345    }
346
347    pub fn local_addr(&self) -> Result<SocketAddr> {
348        Ok(self.listener.local_addr()?)
349    }
350
351    /// True when connections are TLS sessions.
352    pub fn encrypted(&self) -> bool {
353        self.tls.is_some()
354    }
355
356    pub fn limits(&self) -> ServerLimits {
357        self.gate.limits
358    }
359
360    pub fn acl(&self) -> &Acl {
361        &self.acl
362    }
363
364    pub fn served(&self) -> Vec<ServedSet> {
365        let mut out: Vec<ServedSet> = self
366            .sets
367            .iter()
368            .map(|(id, s)| ServedSet {
369                set_id: hex::encode(id),
370                archive_name: s.set.archive_name.clone(),
371                archive_size: s.set.archive_size,
372                volumes: s.volumes.iter().enumerate().filter(|(_, p)| p.is_some()).map(|(i, _)| i).collect(),
373            })
374            .collect();
375        out.sort_by(|a, b| a.set_id.cmp(&b.set_id));
376        out
377    }
378
379    /// Accept connections until `stop` is set (checked every 2 ms, so that a sequential client waits
380    /// at most that long between one piece and the next); each admitted request runs
381    /// on its own thread. Excess connections are answered `ERR 503 busy`, connections above the
382    /// per-address rate `ERR 429`, and closed; in TLS mode both are closed without a reply, so
383    /// no handshake is spent on them. Every admitted connection runs under a time budget
384    /// (`ServerLimits::timeout`, extended per response by `min_rate`), so a peer that trickles
385    /// bytes or stops reading releases its slot when the budget ends.
386    pub fn run(self, stop: Arc<AtomicBool>) -> Result<()> {
387        self.listener.set_nonblocking(true)?;
388        loop {
389            if stop.load(Ordering::Relaxed) {
390                return Ok(());
391            }
392            match self.listener.accept() {
393                Ok((mut stream, peer)) => {
394                    let limits = self.gate.limits;
395                    let _ = stream.set_nonblocking(false);
396                    let _ = stream.set_read_timeout(Some(limits.timeout));
397                    let _ = stream.set_write_timeout(Some(limits.timeout));
398                    match self.gate.enter(peer.ip()) {
399                        Admit::Ok => {}
400                        refused => {
401                            if self.tls.is_none() {
402                                let _ = match refused {
403                                    Admit::Busy => send_err(&mut stream, 503, "busy: too many connections"),
404                                    _ => send_err(&mut stream, 429, "too many requests: slow down"),
405                                };
406                            }
407                            continue;
408                        }
409                    }
410                    let sets = self.sets.clone();
411                    let gate = self.gate.clone();
412                    let tls = self.tls.clone();
413                    let acl = self.acl.clone();
414                    std::thread::spawn(move || {
415                        let mut stream = Deadline::new(stream, limits.timeout, gate.clone());
416                        match tls {
417                            None => {
418                                let _ = handle(&mut stream, &sets, &limits, &acl, None);
419                            }
420                            Some(cfg) => {
421                                if let Ok(conn) = ServerConnection::new(cfg) {
422                                    let mut session = StreamOwned::new(conn, stream);
423                                    // The handshake (with the client's certificate check) completes before any
424                                    // request is read; when it fails the connection is simply closed.
425                                    if session.conn.complete_io(&mut session.sock).is_ok() && !session.conn.is_handshaking() {
426                                        // the verified client certificate, when the configuration asked for one
427                                        let client = session.conn.peer_certificates().and_then(|c| c.first()).map(tls::fingerprint);
428                                        let _ = handle(&mut session, &sets, &limits, &acl, client);
429                                        session.conn.send_close_notify();
430                                        let _ = session.flush();
431                                    }
432                                }
433                            }
434                        }
435                        gate.leave(peer.ip());
436                    });
437                }
438                Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => std::thread::sleep(Duration::from_millis(2)),
439                Err(e) => return Err(Error::Io(e)),
440            }
441        }
442    }
443}
444
445/// A TCP stream whose reads and writes must each finish within the per-operation timeout and
446/// all of them before an absolute deadline: a peer that trickles bytes or stops reading cannot
447/// hold a connection (and its slot) beyond the budget.
448struct Deadline {
449    inner: TcpStream,
450    deadline: Instant,
451    op: Duration,
452    /// The server's gate, for the shared bandwidth bucket.
453    gate: Arc<Gate>,
454}
455
456impl Deadline {
457    fn new(inner: TcpStream, op: Duration, gate: Arc<Gate>) -> Deadline {
458        Deadline { inner, deadline: Instant::now() + op, op, gate }
459    }
460
461    fn slice(&self) -> std::io::Result<Duration> {
462        let left = self.deadline.saturating_duration_since(Instant::now());
463        if left.is_zero() {
464            return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "connection time budget exhausted"));
465        }
466        Ok(left.min(self.op).max(Duration::from_millis(1)))
467    }
468}
469
470impl Read for Deadline {
471    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
472        let t = self.slice()?;
473        self.inner.set_read_timeout(Some(t))?;
474        self.inner.read(buf)
475    }
476}
477
478impl Write for Deadline {
479    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
480        // Every byte the server sends (status lines, payloads, TLS records) passes here. With a
481        // bandwidth cap the write is clamped to one slice, paid from the shared bucket, and the
482        // time spent waiting is credited to this connection's budget: throttling by the server
483        // must never look like a client that reads too slowly.
484        let mut n = buf.len();
485        if let Some(max) = self.gate.slice_len() {
486            n = n.min(max).max(1);
487            let wait = self.gate.take_bytes(n);
488            if !wait.is_zero() {
489                std::thread::sleep(wait);
490                self.deadline += wait;
491            }
492        }
493        let t = self.slice()?;
494        self.inner.set_write_timeout(Some(t))?;
495        self.inner.write(&buf[..n])
496    }
497
498    fn flush(&mut self) -> std::io::Result<()> {
499        self.inner.flush()
500    }
501}
502
503/// Extends a connection's time budget for a response of a known size (`ServerLimits::min_rate`).
504trait Budget {
505    fn allow(&mut self, bytes: u64, limits: &ServerLimits);
506}
507
508impl Budget for Deadline {
509    fn allow(&mut self, bytes: u64, limits: &ServerLimits) {
510        let secs = (bytes / limits.min_rate.max(1)).max(limits.timeout.as_secs());
511        self.deadline = Instant::now() + Duration::from_secs(secs);
512    }
513}
514
515impl<C, T: Read + Write + Budget> Budget for StreamOwned<C, T> {
516    fn allow(&mut self, bytes: u64, limits: &ServerLimits) {
517        self.sock.allow(bytes, limits)
518    }
519}
520
521/// Read one newline-terminated line of at most `MAX_LINE` bytes of printable ASCII (tab and CR
522/// tolerated). A byte outside that set fails at once, without waiting for a newline: a TLS
523/// record or any other binary stream sent to a plain endpoint is refused immediately instead
524/// of holding the connection until the timeout.
525fn read_line_bounded<R: BufRead>(r: &mut R) -> std::io::Result<String> {
526    let mut buf = Vec::with_capacity(64);
527    loop {
528        let available = r.fill_buf()?;
529        if available.is_empty() {
530            return Err(std::io::Error::other("request line too long or unterminated"));
531        }
532        let mut used = 0;
533        let mut complete = false;
534        for &b in available {
535            used += 1;
536            if b == b'\n' {
537                complete = true;
538                break;
539            }
540            if !(b == b'\t' || b == b'\r' || (0x20..0x7f).contains(&b)) {
541                r.consume(used);
542                return Err(std::io::Error::other("request line is not ASCII"));
543            }
544            buf.push(b);
545            if buf.len() >= MAX_LINE {
546                r.consume(used);
547                return Err(std::io::Error::other("request line too long or unterminated"));
548            }
549        }
550        r.consume(used);
551        if complete {
552            return String::from_utf8(buf).map_err(|_| std::io::Error::other("request line is not ASCII"));
553        }
554    }
555}
556
557fn send_err<W: Write>(stream: &mut W, code: u16, msg: &str) -> std::io::Result<()> {
558    stream.write_all(format!("ERR {code} {msg}\n").as_bytes())?;
559    stream.flush()
560}
561
562fn send_ok<W: Write>(stream: &mut W, payload: &[u8]) -> std::io::Result<()> {
563    stream.write_all(format!("OK {}\n", payload.len()).as_bytes())?;
564    stream.write_all(payload)?;
565    stream.flush()
566}
567
568/// Stream `len` bytes of `file` from `off` without holding the whole piece in memory.
569fn send_ok_from_file<W: Write>(stream: &mut W, file: &mut File, off: u64, len: usize) -> std::io::Result<()> {
570    file.seek(SeekFrom::Start(off))?;
571    stream.write_all(format!("OK {len}\n").as_bytes())?;
572    let mut buf = vec![0u8; STREAM_CHUNK.min(len)];
573    let mut left = len;
574    while left > 0 {
575        let n = buf.len().min(left);
576        file.read_exact(&mut buf[..n])?;
577        stream.write_all(&buf[..n])?;
578        left -= n;
579    }
580    stream.flush()
581}
582
583/// Serve one request on an accepted connection (plain or TLS: anything that reads and writes
584/// under a time budget). `client` is the verified certificate fingerprint of a mutually
585/// authenticated client (`None` for plain and anonymous connections); `acl` decides which sets it
586/// may read, and a set it may not read is answered exactly like an unknown one.
587fn handle<S: Read + Write + Budget>(stream: &mut S, sets: &HashMap<[u8; 32], Served>, limits: &ServerLimits, acl: &Acl, client: Option<[u8; 32]>) -> std::io::Result<()> {
588    let line = {
589        let mut reader = BufReader::new(&mut *stream);
590        read_line_bounded(&mut reader)
591    };
592    let line = match line {
593        Ok(l) => l,
594        Err(e) => return send_err(stream, 400, &e.to_string()),
595    };
596    let parts: Vec<&str> = line.split(' ').collect();
597    if parts.first() != Some(&PROTOCOL) || parts.len() < 2 {
598        return send_err(stream, 400, "expected a TSXP/1 request");
599    }
600    match parts[1] {
601        "SETS" => {
602            let list: Vec<(String, String, u64)> =
603                sets.iter().filter(|(id, _)| acl.may_read(client.as_ref(), id)).map(|(id, s)| (hex::encode(id), s.set.archive_name.clone(), s.set.archive_size)).collect();
604            let bytes = format::cbor_encode(&list).map_err(|e| std::io::Error::other(e.to_string()))?;
605            stream.allow(bytes.len() as u64, limits);
606            send_ok(stream, &bytes)
607        }
608        "DESCRIPTOR" | "HAVE" | "PIECE" => {
609            let Some(id) = parts.get(2).and_then(|h| parse_hex32(h)) else { return send_err(stream, 400, "bad set id") };
610            // a set the client may not read is indistinguishable from one that is not served
611            let Some(s) = sets.get(&id).filter(|_| acl.may_read(client.as_ref(), &id)) else { return send_err(stream, 404, "unknown set") };
612            match parts[1] {
613                "DESCRIPTOR" => {
614                    stream.allow(s.descriptor.len() as u64, limits);
615                    send_ok(stream, &s.descriptor)
616                }
617                "HAVE" => {
618                    let have: Vec<u16> = s.volumes.iter().enumerate().filter(|(_, p)| p.is_some()).map(|(i, _)| i as u16).collect();
619                    let bytes = format::cbor_encode(&have).map_err(|e| std::io::Error::other(e.to_string()))?;
620                    stream.allow(bytes.len() as u64, limits);
621                    send_ok(stream, &bytes)
622                }
623                _ => {
624                    let (Some(vi), Some(st)) = (parts.get(3).and_then(|x| x.parse::<usize>().ok()), parts.get(4).and_then(|x| x.parse::<u64>().ok())) else {
625                        return send_err(stream, 400, "bad piece address");
626                    };
627                    if parts.len() != 5 || vi >= s.set.total() || st >= s.set.volume_pieces(vi) {
628                        return send_err(stream, 404, "no such piece");
629                    }
630                    let Some(path) = &s.volumes[vi] else { return send_err(stream, 404, "volume not available here") };
631                    let ps = s.set.piece_size as usize;
632                    let mut f = match File::open(path) {
633                        Ok(f) => f,
634                        Err(e) => return send_err(stream, 500, &e.to_string()),
635                    };
636                    stream.allow(ps as u64, limits);
637                    send_ok_from_file(stream, &mut f, HEADER_LEN as u64 + st * ps as u64, ps)
638                }
639            }
640        }
641        _ => send_err(stream, 400, "unknown request"),
642    }
643}
644
645// ---------------------------------------------------------------- client
646
647fn io_peer(peer: &str, e: std::io::Error) -> Error {
648    Error::Io(std::io::Error::new(e.kind(), format!("{peer}: {e}")))
649}
650
651/// A connection that ended before a reply: in TLS mode this is how a server declines a
652/// connection above its limits (no protocol reply is sent), so the client treats it like a
653/// `429`/`503` and retries with the same bounded back-off.
654fn closed_early(e: &std::io::Error) -> bool {
655    use std::io::ErrorKind::{BrokenPipe, ConnectionAborted, ConnectionReset, UnexpectedEof};
656    matches!(e.kind(), UnexpectedEof | ConnectionReset | ConnectionAborted | BrokenPipe)
657}
658
659/// Time spent by a client, accumulated over its requests (one connection each).
660#[derive(Clone, Copy, Debug, Default)]
661pub struct Timing {
662    pub requests: usize,
663    pub bytes: u64,
664    /// TCP connect.
665    pub connect: Duration,
666    /// TLS handshake (zero in plain mode).
667    pub handshake: Duration,
668    /// From sending the request line to the last payload byte.
669    pub transfer: Duration,
670}
671
672/// A parsed reply: a payload, or the server's refusal (`ERR <code> <message>`).
673enum Reply {
674    Ok(Vec<u8>),
675    Refused(u16, String),
676}
677
678fn refusal(peer: &str, code: u16, msg: &str) -> Error {
679    Error::Missing(format!("{peer}: {code} {msg}"))
680}
681
682/// One request; the response payload is refused (never allocated) above `max_len` bytes. With
683/// `tls` the connection is a TLS session pinned to the peer's fingerprint (`tls::client_config`);
684/// a peer that presents another certificate fails during the handshake, before the request is
685/// sent. Connect, handshake and transfer times are added to `timing`.
686fn request(peer: &str, tls: Option<&Arc<ClientConfig>>, line: &str, max_len: usize, timing: &mut Timing) -> Result<Reply> {
687    let addr = peer.to_socket_addrs().ok().and_then(|mut a| a.next()).ok_or_else(|| Error::Invalid(format!("peer address {peer}")))?;
688    let t0 = Instant::now();
689    let stream = TcpStream::connect_timeout(&addr, TIMEOUT).map_err(|e| io_peer(peer, e))?;
690    stream.set_read_timeout(Some(TIMEOUT))?;
691    stream.set_write_timeout(Some(TIMEOUT))?;
692    let t1 = Instant::now();
693    timing.connect += t1 - t0;
694    match tls {
695        None => exchange(stream, peer, line, max_len, timing),
696        Some(cfg) => {
697            let conn = ClientConnection::new(cfg.clone(), tls::server_name()).map_err(|e| Error::Crypto(format!("{peer}: tls: {e}")))?;
698            let mut session = StreamOwned::new(conn, stream);
699            // finish the handshake (and the pinned-fingerprint check) before the request is sent
700            session.conn.complete_io(&mut session.sock).map_err(|e| io_peer(peer, e))?;
701            timing.handshake += t1.elapsed();
702            exchange(session, peer, line, max_len, timing)
703        }
704    }
705}
706
707fn exchange<S: Read + Write>(mut stream: S, peer: &str, line: &str, max_len: usize, timing: &mut Timing) -> Result<Reply> {
708    let t0 = Instant::now();
709    stream.write_all(format!("{PROTOCOL} {line}\n").as_bytes()).map_err(|e| io_peer(peer, e))?;
710    stream.flush().map_err(|e| io_peer(peer, e))?;
711    let mut reader = BufReader::new(stream);
712    let status = read_line_bounded(&mut reader).map_err(|e| io_peer(peer, e))?;
713    let mut words = status.splitn(3, ' ');
714    match (words.next(), words.next(), words.next()) {
715        (Some("OK"), Some(len), _) => {
716            let len: usize = len.parse().map_err(|_| Error::Corrupt(format!("{peer}: bad response length")))?;
717            if len > max_len {
718                return Err(Error::Limit(format!("{peer}: response of {len} bytes exceeds the {max_len}-byte limit")));
719            }
720            let mut buf = vec![0u8; len];
721            reader.read_exact(&mut buf).map_err(|e| io_peer(peer, e))?;
722            timing.transfer += t0.elapsed();
723            timing.requests += 1;
724            timing.bytes += len as u64;
725            Ok(Reply::Ok(buf))
726        }
727        (Some("ERR"), code, msg) => Ok(Reply::Refused(code.and_then(|c| c.parse().ok()).unwrap_or(0), msg.unwrap_or("").to_string())),
728        _ => Err(Error::Corrupt(format!("{peer}: unexpected response"))),
729    }
730}
731
732/// TLS settings of a client: `peer_ids[i]` is the expected certificate fingerprint of
733/// `FetchOptions::peers[i]` (same length, same order); `identity` is presented to servers that
734/// require client certificates and may be `None` for servers that accept anonymous clients;
735/// `revoked` fingerprints are refused even when pinned (a pinned peer that is revoked makes the
736/// whole fetch fail before any connection: skipping it silently would make a mistake in the
737/// wrong file look like an unreachable peer).
738#[derive(Clone, Default)]
739pub struct ClientTls {
740    pub identity: Option<tls::Identity>,
741    pub peer_ids: Vec<[u8; 32]>,
742    pub revoked: Revocations,
743}
744
745/// The per-peer connection settings of one fetch, built once, plus the accumulated timing.
746struct Dial {
747    configs: HashMap<String, Option<Arc<ClientConfig>>>,
748    timing: RefCell<Timing>,
749}
750
751impl Dial {
752    fn new(peers: &[String], tls: Option<&ClientTls>) -> Result<Dial> {
753        if let Some(t) = tls {
754            if t.peer_ids.len() != peers.len() {
755                return Err(Error::Invalid(format!("{} peer addresses but {} expected peer identities: give one identity per peer, in the same order", peers.len(), t.peer_ids.len())));
756            }
757        }
758        let mut configs = HashMap::new();
759        for (i, peer) in peers.iter().enumerate() {
760            let cfg = match tls {
761                None => None,
762                Some(t) => {
763                    if t.revoked.contains(&t.peer_ids[i]) {
764                        return Err(Error::Policy(format!("peer identity {} ({peer}) is revoked; edit the command instead of skipping the peer", hex::encode(t.peer_ids[i]))));
765                    }
766                    Some(tls::client_config_with(t.identity.as_ref(), t.peer_ids[i], &t.revoked)?)
767                }
768            };
769            configs.insert(peer.clone(), cfg);
770        }
771        Ok(Dial { configs, timing: RefCell::new(Timing::default()) })
772    }
773
774    /// One request with a bounded back-off when the peer answers `429` (rate) or `503` (busy),
775    /// or closes the connection before replying (the TLS-mode form of the same refusals): about
776    /// two and a half seconds in total, then the refusal is reported.
777    fn request(&self, peer: &str, line: &str, max_len: usize) -> Result<Vec<u8>> {
778        let cfg = self.configs.get(peer).and_then(|c| c.as_ref());
779        let mut attempt = 0u32;
780        loop {
781            let outcome = request(peer, cfg, line, max_len, &mut self.timing.borrow_mut());
782            let refused = match outcome {
783                Ok(Reply::Ok(bytes)) => return Ok(bytes),
784                Ok(Reply::Refused(code @ (429 | 503), msg)) => refusal(peer, code, &msg),
785                Ok(Reply::Refused(code, msg)) => return Err(refusal(peer, code, &msg)),
786                Err(Error::Io(e)) if closed_early(&e) => Error::Io(e),
787                Err(e) => return Err(e),
788            };
789            if attempt >= 16 {
790                return Err(refused);
791            }
792            attempt += 1;
793            std::thread::sleep(Duration::from_millis(u64::from(25 * attempt.min(8))));
794        }
795    }
796}
797
798/// Which volumes a fetch should bring to the local side.
799#[derive(Clone, Debug, PartialEq, Eq)]
800pub enum Want {
801    /// The fewest volumes that make the set reconstructible locally (data volumes first).
802    Needed,
803    /// Every volume of the set.
804    All,
805    /// Specific volume indices (0-based).
806    Volumes(Vec<usize>),
807}
808
809pub struct FetchOptions {
810    pub set_id: [u8; 32],
811    /// When known, the received descriptor must hash to this value; otherwise only the set id
812    /// (archive hash + geometry) is verified and piece hashes stay provisional until `join`.
813    pub descriptor_b3: Option<[u8; 32]>,
814    pub peers: Vec<String>,
815    pub out_dir: PathBuf,
816    /// Volumes (files or directories) already present locally, besides `out_dir`.
817    pub local: Vec<PathBuf>,
818    pub want: Want,
819    /// Stop after this many pieces (used to exercise interruption and resume).
820    pub max_pieces: Option<usize>,
821    /// `None`: plain TCP. `Some`: every connection is TLS, pinned per peer (see `ClientTls`).
822    pub tls: Option<ClientTls>,
823}
824
825#[derive(Clone, Debug, Serialize)]
826pub struct FetchReport {
827    pub set_id: String,
828    pub descriptor_b3: String,
829    /// "descriptor" when the descriptor hash was supplied and matched, else "set id".
830    pub verified_against: &'static str,
831    pub volumes_wanted: Vec<usize>,
832    pub volumes_completed: Vec<usize>,
833    pub volumes_partial: Vec<usize>,
834    pub pieces_received: usize,
835    /// Pieces or descriptors from peers that failed verification or arrived malformed.
836    pub pieces_rejected: usize,
837    /// Pieces of partial files that were re-read on resume and matched their hash.
838    pub pieces_reverified: usize,
839    /// Pieces the map declared complete whose bytes on disk did not match (fetched again).
840    pub pieces_reverify_failed: usize,
841    /// Bytes reserved on disk for the wanted volumes (full volume sizes).
842    pub bytes_reserved: u64,
843    pub peers_used: Vec<String>,
844    /// After the fetch, with everything local (existing volumes + out_dir).
845    pub reconstructible: bool,
846    pub stopped_early: bool,
847    /// True when the connections were TLS sessions with pinned peer identities.
848    pub encrypted: bool,
849    /// Connections opened (one per request) and payload bytes received.
850    pub requests: usize,
851    pub bytes_received: u64,
852    /// Seconds spent in TCP connects, TLS handshakes, request/response transfers, and in total
853    /// (including local verification and disk writes).
854    pub seconds_connect: f64,
855    pub seconds_handshake: f64,
856    pub seconds_transfer: f64,
857    pub seconds_total: f64,
858}
859
860/// Progress map of a volume being fetched, stored next to the `.partial` file (a hint only).
861#[derive(Clone, Debug, Serialize, Deserialize)]
862struct PartialMap {
863    descriptor_b3: ByteBuf,
864    index: u16,
865    have: Vec<bool>,
866}
867
868fn partial_paths(out_dir: &Path, set: &VolumeSet, vi: usize) -> (PathBuf, PathBuf, PathBuf) {
869    let final_path = out_dir.join(set.volume_name(vi));
870    let partial = out_dir.join(format!("{}.partial", set.volume_name(vi)));
871    let map = out_dir.join(format!("{}.partial.map", set.volume_name(vi)));
872    (final_path, partial, map)
873}
874
875/// Write the map atomically (temporary file + rename) so that an interruption leaves the old map.
876fn save_map(map_path: &Path, map: &PartialMap) -> Result<()> {
877    let tmp = map_path.with_extension("map.tmp");
878    std::fs::write(&tmp, format::cbor_encode(map)?)?;
879    if map_path.exists() {
880        std::fs::remove_file(map_path)?;
881    }
882    std::fs::rename(&tmp, map_path)?;
883    Ok(())
884}
885
886fn expected_piece_hash(set: &VolumeSet, vi: usize, s: usize) -> [u8; 32] {
887    let n = set.data as usize;
888    if set.is_parity(vi) {
889        set.parity_hash(s as u32, vi - n)
890    } else {
891        set.piece_hash((s as u64 * n as u64 + vi as u64) as u32)
892    }
893}
894
895/// Read piece `s` of the partial file and compare it with the descriptor hash.
896fn piece_on_disk_ok(file: &mut File, set: &VolumeSet, vi: usize, s: usize, buf: &mut [u8]) -> bool {
897    let ps = set.piece_size as usize;
898    if file.seek(SeekFrom::Start(HEADER_LEN as u64 + s as u64 * ps as u64)).is_err() {
899        return false;
900    }
901    let mut got = 0;
902    while got < ps {
903        match file.read(&mut buf[got..]) {
904            Ok(0) | Err(_) => return false,
905            Ok(n) => got += n,
906        }
907    }
908    chunk::hash(&buf[..ps]) == expected_piece_hash(set, vi, s)
909}
910
911/// Obtain and verify the descriptor: from a local volume when one exists, else from the peers.
912fn obtain_descriptor(opts: &FetchOptions, dial: &Dial, peers_used: &mut Vec<String>) -> Result<(VolumeSet, [u8; 32], &'static str)> {
913    let mut local_paths = opts.local.clone();
914    if opts.out_dir.is_dir() {
915        local_paths.push(opts.out_dir.clone());
916    }
917    if !local_paths.is_empty() {
918        if let Ok(sets) = volumes::inspect(&local_paths, false) {
919            for st in sets {
920                if st.set_id == hex::encode(opts.set_id) {
921                    let bytes = format::cbor_encode(&st.set)?;
922                    let h = chunk::hash(&bytes);
923                    if let Some(expected) = opts.descriptor_b3 {
924                        if h != expected {
925                            return Err(Error::Corrupt("the local volumes carry a descriptor that does not match the expected descriptor hash".into()));
926                        }
927                    }
928                    return Ok((st.set, h, if opts.descriptor_b3.is_some() { "descriptor" } else { "set id" }));
929                }
930            }
931        }
932    }
933    let mut last_err = None;
934    for peer in &opts.peers {
935        match dial.request(peer, &format!("DESCRIPTOR {}", hex::encode(opts.set_id)), MAX_DESCRIPTOR as usize) {
936            Ok(bytes) => match verify_descriptor(&bytes, opts) {
937                Ok((set, h, how)) => {
938                    peers_used.push(peer.clone());
939                    return Ok((set, h, how));
940                }
941                Err(e) => last_err = Some(format!("{peer}: {e}")),
942            },
943            Err(e) => last_err = Some(e.to_string()),
944        }
945    }
946    Err(Error::Missing(format!("no peer supplied an acceptable descriptor ({})", last_err.unwrap_or_else(|| "no peers".into()))))
947}
948
949fn verify_descriptor(bytes: &[u8], opts: &FetchOptions) -> Result<(VolumeSet, [u8; 32], &'static str)> {
950    let h = chunk::hash(bytes);
951    if let Some(expected) = opts.descriptor_b3 {
952        if h != expected {
953            return Err(Error::HashMismatch("descriptor hash".into()));
954        }
955    }
956    let set: VolumeSet = format::cbor_decode(bytes)?;
957    set.validate()?;
958    let recomputed = volumes::set_id_for(&hash32(&set.archive_b3), set.archive_size, set.piece_size, set.data, set.parity);
959    if recomputed != opts.set_id || set.set_id[..] != opts.set_id[..] {
960        return Err(Error::HashMismatch("descriptor does not belong to the expected set id".into()));
961    }
962    Ok((set, h, if opts.descriptor_b3.is_some() { "descriptor" } else { "set id" }))
963}
964
965/// Fetch the wanted volumes from the peers into `out_dir`, verifying everything on the way.
966pub fn fetch(opts: &FetchOptions) -> Result<FetchReport> {
967    if opts.peers.is_empty() {
968        return Err(Error::Invalid("at least one peer address is required".into()));
969    }
970    let started = Instant::now();
971    let dial = Dial::new(&opts.peers, opts.tls.as_ref())?;
972    std::fs::create_dir_all(&opts.out_dir)?;
973    let mut peers_used: Vec<String> = Vec::new();
974    let (set, descriptor_b3, verified_against) = obtain_descriptor(opts, &dial, &mut peers_used)?;
975    let descriptor_bytes = format::cbor_encode(&set)?;
976
977    // what is complete locally already
978    let mut local_paths = opts.local.clone();
979    local_paths.push(opts.out_dir.clone());
980    let local_complete: Vec<usize> = match volumes::inspect(&local_paths, true) {
981        Ok(sets) => sets
982            .into_iter()
983            .find(|s| s.set_id == hex::encode(opts.set_id))
984            .map(|s| s.volumes.iter().enumerate().filter(|(_, v)| v.as_ref().is_some_and(|f| f.usable())).map(|(i, _)| i).collect())
985            .unwrap_or_default(),
986        Err(_) => Vec::new(),
987    };
988
989    // what every peer has
990    let mut availability: BTreeMap<usize, Vec<String>> = BTreeMap::new();
991    for peer in &opts.peers {
992        if let Ok(bytes) = dial.request(peer, &format!("HAVE {}", hex::encode(opts.set_id)), MAX_HAVE_RESPONSE) {
993            if let Ok(have) = format::cbor_decode::<Vec<u16>>(&bytes) {
994                for vi in have {
995                    if (vi as usize) < set.total() {
996                        availability.entry(vi as usize).or_default().push(peer.clone());
997                    }
998                }
999            }
1000        }
1001    }
1002
1003    let n = set.data as usize;
1004    let wanted: Vec<usize> = match &opts.want {
1005        Want::All => (0..set.total()).filter(|vi| !local_complete.contains(vi)).collect(),
1006        Want::Volumes(list) => list.iter().copied().filter(|vi| *vi < set.total() && !local_complete.contains(vi)).collect(),
1007        Want::Needed => {
1008            let mut chosen = Vec::new();
1009            let mut have = local_complete.len();
1010            for vi in (0..n).chain(n..set.total()) {
1011                if have >= n {
1012                    break;
1013                }
1014                if !local_complete.contains(&vi) && availability.contains_key(&vi) {
1015                    chosen.push(vi);
1016                    have += 1;
1017                }
1018            }
1019            if have < n {
1020                return Err(Error::Missing(format!(
1021                    "the peers hold only {} of the {n} volumes needed (local {}, available remotely {:?})",
1022                    have,
1023                    local_complete.len(),
1024                    availability.keys().collect::<Vec<_>>()
1025                )));
1026            }
1027            chosen
1028        }
1029    };
1030
1031    let ps = set.piece_size as usize;
1032    let mut buf = vec![0u8; ps];
1033    let mut received = 0usize;
1034    let mut rejected = 0usize;
1035    let mut reverified = 0usize;
1036    let mut reverify_failed = 0usize;
1037    let mut bytes_reserved = 0u64;
1038    let mut completed = Vec::new();
1039    let mut partial = Vec::new();
1040    let mut stopped_early = false;
1041    'volumes: for &vi in &wanted {
1042        let (final_path, partial_path, map_path) = partial_paths(&opts.out_dir, &set, vi);
1043        let count = set.volume_pieces(vi) as usize;
1044        let full_len = HEADER_LEN as u64 + set.payload_len(vi);
1045        // resume state: the map is a hint; every piece it claims is read back and hash-checked
1046        let hint: Option<Vec<bool>> = match std::fs::read(&map_path).ok().and_then(|b| format::cbor_decode::<PartialMap>(&b).ok()) {
1047            Some(m) if m.descriptor_b3[..] == descriptor_b3[..] && m.index as usize == vi && m.have.len() == count => Some(m.have),
1048            Some(_) => None, // a map of another set or geometry: trust nothing it says
1049            None => None,    // missing or damaged map: check every position of the partial file
1050        };
1051        let mut have = vec![false; count];
1052        if partial_path.is_file() {
1053            let mut f = OpenOptions::new().read(true).write(true).open(&partial_path)?;
1054            let mut header = [0u8; HEADER_LEN];
1055            let header_ok =
1056                f.read_exact(&mut header).is_ok() && volumes::decode_header(&header).is_ok_and(|h| h.index as usize == vi && h.set_id[..] == set.set_id[..] && h.piece_size == set.piece_size);
1057            if header_ok {
1058                for (s, slot) in have.iter_mut().enumerate() {
1059                    let claimed = hint.as_ref().is_none_or(|h| h[s]);
1060                    if !claimed {
1061                        continue;
1062                    }
1063                    if piece_on_disk_ok(&mut f, &set, vi, s, &mut buf) {
1064                        *slot = true;
1065                        reverified += 1;
1066                    } else if hint.is_some() {
1067                        reverify_failed += 1;
1068                    }
1069                }
1070                if f.metadata()?.len() != full_len {
1071                    f.set_len(full_len)?;
1072                }
1073            } else {
1074                drop(f);
1075                std::fs::remove_file(&partial_path)?;
1076            }
1077        }
1078        if !partial_path.is_file() {
1079            let mut f = File::create(&partial_path)?;
1080            let mut id = [0u8; 32];
1081            id.copy_from_slice(&set.set_id);
1082            f.write_all(&volumes::encode_header(&volumes::VolumeHeader {
1083                index: vi as u16,
1084                data: set.data,
1085                parity: set.parity,
1086                piece_size: set.piece_size,
1087                archive_size: set.archive_size,
1088                set_id: id,
1089                payload_len: set.payload_len(vi),
1090            }))?;
1091            f.set_len(full_len)?;
1092        }
1093        bytes_reserved += full_len;
1094        let mut map = PartialMap { descriptor_b3: ByteBuf::from(descriptor_b3.to_vec()), index: vi as u16, have };
1095        save_map(&map_path, &map)?;
1096        let mut file = OpenOptions::new().read(true).write(true).open(&partial_path)?;
1097        let sources: Vec<String> = availability.get(&vi).cloned().unwrap_or_default();
1098        if sources.is_empty() && !map.have.iter().all(|&h| h) {
1099            partial.push(vi);
1100            continue;
1101        }
1102        let mut next_source = 0usize;
1103        for s in 0..count {
1104            if map.have[s] {
1105                continue;
1106            }
1107            if opts.max_pieces.is_some_and(|max| received >= max) {
1108                stopped_early = true;
1109                save_map(&map_path, &map)?;
1110                partial.push(vi);
1111                break 'volumes;
1112            }
1113            let expected = expected_piece_hash(&set, vi, s);
1114            let mut got = None;
1115            for attempt in 0..sources.len() {
1116                let peer = &sources[(next_source + attempt) % sources.len()];
1117                match dial.request(peer, &format!("PIECE {} {vi} {s}", hex::encode(opts.set_id)), ps) {
1118                    Ok(bytes) if bytes.len() == ps && chunk::hash(&bytes) == expected => {
1119                        got = Some(bytes);
1120                        if !peers_used.contains(peer) {
1121                            peers_used.push(peer.clone());
1122                        }
1123                        next_source = (next_source + attempt) % sources.len();
1124                        break;
1125                    }
1126                    Ok(_) => rejected += 1,
1127                    Err(_) => rejected += 1,
1128                }
1129            }
1130            let Some(bytes) = got else {
1131                save_map(&map_path, &map)?;
1132                return Err(Error::Missing(format!("piece {s} of {} could not be obtained from any peer", set.volume_name(vi))));
1133            };
1134            file.seek(SeekFrom::Start(HEADER_LEN as u64 + s as u64 * ps as u64))?;
1135            file.write_all(&bytes)?;
1136            file.flush()?;
1137            map.have[s] = true;
1138            received += 1;
1139            save_map(&map_path, &map)?;
1140        }
1141        if map.have.iter().all(|&h| h) {
1142            file.seek(SeekFrom::Start(HEADER_LEN as u64 + set.payload_len(vi)))?;
1143            file.write_all(&descriptor_bytes)?;
1144            file.write_all(&volumes::encode_trailer(HEADER_LEN as u64 + set.payload_len(vi), descriptor_bytes.len() as u64, &descriptor_b3, vi as u16))?;
1145            file.flush()?;
1146            drop(file);
1147            if final_path.exists() {
1148                std::fs::remove_file(&final_path)?;
1149            }
1150            std::fs::rename(&partial_path, &final_path)?;
1151            let _ = std::fs::remove_file(&map_path);
1152            completed.push(vi);
1153        }
1154    }
1155    let reconstructible = volumes::inspect(&local_paths, true).ok().and_then(|sets| sets.into_iter().find(|s| s.set_id == hex::encode(opts.set_id))).is_some_and(|s| s.reconstructible);
1156    let timing = *dial.timing.borrow();
1157    Ok(FetchReport {
1158        set_id: hex::encode(opts.set_id),
1159        descriptor_b3: hex::encode(descriptor_b3),
1160        verified_against,
1161        volumes_wanted: wanted,
1162        volumes_completed: completed,
1163        volumes_partial: partial,
1164        pieces_received: received,
1165        pieces_rejected: rejected,
1166        pieces_reverified: reverified,
1167        pieces_reverify_failed: reverify_failed,
1168        bytes_reserved,
1169        peers_used,
1170        reconstructible,
1171        stopped_early,
1172        encrypted: opts.tls.is_some(),
1173        requests: timing.requests,
1174        bytes_received: timing.bytes,
1175        seconds_connect: timing.connect.as_secs_f64(),
1176        seconds_handshake: timing.handshake.as_secs_f64(),
1177        seconds_transfer: timing.transfer.as_secs_f64(),
1178        seconds_total: started.elapsed().as_secs_f64(),
1179    })
1180}
1181
1182/// List the sets a peer serves; with `peer_id` the connection is TLS pinned to that
1183/// fingerprint, presenting `identity` when given.
1184pub fn list_peer(peer: &str, identity: Option<&tls::Identity>, peer_id: Option<[u8; 32]>) -> Result<Vec<(String, String, u64)>> {
1185    list_peer_with(peer, identity, peer_id, &Revocations::default())
1186}
1187
1188/// `list_peer` with a revocation list (`ClientTls::revoked`): a revoked peer is refused before
1189/// any connection, and would fail the handshake anyway.
1190pub fn list_peer_with(peer: &str, identity: Option<&tls::Identity>, peer_id: Option<[u8; 32]>, revoked: &Revocations) -> Result<Vec<(String, String, u64)>> {
1191    let cfg = match peer_id {
1192        Some(id) if revoked.contains(&id) => return Err(Error::Policy(format!("peer identity {} ({peer}) is revoked", hex::encode(id)))),
1193        Some(id) => Some(tls::client_config_with(identity, id, revoked)?),
1194        None => None,
1195    };
1196    match request(peer, cfg.as_ref(), "SETS", MAX_SETS_RESPONSE, &mut Timing::default())? {
1197        Reply::Ok(bytes) => format::cbor_decode(&bytes),
1198        Reply::Refused(code, msg) => Err(refusal(peer, code, &msg)),
1199    }
1200}
1201
1202#[cfg(test)]
1203mod tests {
1204    use super::*;
1205
1206    fn ip(last: u8) -> IpAddr {
1207        IpAddr::from([10, 0, 0, last])
1208    }
1209
1210    #[test]
1211    fn gate_refuses_a_new_address_beyond_max_peers() {
1212        let g = Gate::new(ServerLimits { max_peers: 2, ..ServerLimits::default() });
1213        assert_eq!(g.enter(ip(1)), Admit::Ok);
1214        assert_eq!(g.enter(ip(2)), Admit::Ok);
1215        assert_eq!(g.enter(ip(3)), Admit::Busy, "a third address needs a free address slot");
1216        assert_eq!(g.enter(ip(1)), Admit::Ok, "a known address is not a new one");
1217        g.leave(ip(2));
1218        assert_eq!(g.enter(ip(3)), Admit::Ok, "the slot of an address that left is free again");
1219        g.leave(ip(1));
1220        g.leave(ip(1));
1221        g.leave(ip(3));
1222        assert_eq!(g.total.load(Ordering::Relaxed), 0);
1223        assert!(g.per_ip.lock().unwrap().is_empty());
1224    }
1225
1226    #[test]
1227    fn bandwidth_bucket_grants_a_burst_then_meters_the_rate() {
1228        let g = Gate::new(ServerLimits { max_bandwidth: Some(1 << 20), ..ServerLimits::default() });
1229        assert_eq!(g.slice_len(), Some(128 << 10));
1230        // one second of the rate is free (the bucket starts full) ...
1231        assert!(g.take_bytes(1 << 20).is_zero());
1232        // ... the next megabyte has to wait about a second, and the debt accumulates in order
1233        let w1 = g.take_bytes(1 << 20);
1234        let w2 = g.take_bytes(1 << 20);
1235        assert!(w1 > Duration::from_millis(900) && w1 <= Duration::from_millis(1100), "{w1:?}");
1236        assert!(w2 > Duration::from_millis(1900) && w2 <= Duration::from_millis(2100), "{w2:?}");
1237        // without a cap nothing waits and nothing is clamped
1238        let free = Gate::new(ServerLimits::default());
1239        assert_eq!(free.slice_len(), None);
1240        assert!(free.take_bytes(usize::MAX / 2).is_zero());
1241        // the smallest slice is 4 KiB whatever the rate
1242        assert_eq!(Gate::new(ServerLimits { max_bandwidth: Some(1024), ..ServerLimits::default() }).slice_len(), Some(4096));
1243    }
1244}