1use std::collections::{HashSet, VecDeque};
33use std::sync::{Arc, Mutex};
34
35use async_trait::async_trait;
36use tokio::sync::Notify;
37use tokio::task::JoinSet;
38use tokio::time::Instant;
39
40use crate::mesh::{MeshError, MeshStream};
41use crate::mesh_artifact::ArtifactExchange;
42use crate::mesh_artifact_peers::{FetchError, FetchOutcome};
43use crate::mesh_artifact_transfer::{PeerError, send};
44use crate::mesh_artifact_types::{ArtifactDescriptor, ArtifactId, ArtifactKey, Bitfield};
45use crate::mesh_artifact_wire::ArtifactMsg;
46use crate::mesh_swarm_picker::{PeerCandidate, Pick, PiecePicker, order_peers};
47
48#[async_trait]
50pub trait PeerDialer: Send + Sync + 'static {
51 async fn dial(&self, peer_id: &str) -> Result<Box<dyn MeshStream>, MeshError>;
53}
54
55#[derive(Debug, Clone, Default)]
59pub struct Expect {
60 pub size: Option<u64>,
62 pub max_size: Option<u64>,
64 pub root: Option<ArtifactId>,
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct SwarmFetchOptions {
71 pub local_lan: Option<String>,
74 pub expect: Expect,
76}
77
78const MAX_ATTEMPTS: usize = 3;
81
82pub(crate) struct Session {
83 pub(crate) peer: String,
84 pub(crate) stream: Box<dyn MeshStream>,
85 pub(crate) has: Bitfield,
86}
87
88#[derive(Default)]
89struct Totals {
90 pieces: u32,
91 bytes: u64,
92 rejected: u32,
93 sources: Vec<String>,
94 written: HashSet<[u8; 32]>,
95 lost: Vec<(String, String)>,
97 banned: Vec<String>,
98}
99
100struct Shared {
101 picker: PiecePicker,
102 totals: Totals,
103}
104
105fn lock(m: &Mutex<Shared>) -> std::sync::MutexGuard<'_, Shared> {
106 m.lock().unwrap_or_else(|p| p.into_inner())
107}
108
109enum Attempt {
111 Unverified { error: String, peers: Vec<String> },
114 Failed(FetchError),
116}
117
118impl ArtifactExchange {
119 pub(crate) fn check_descriptor(
122 &self,
123 d: &ArtifactDescriptor,
124 expect: &Expect,
125 ) -> Result<(), PeerError> {
126 let cfg = self.config();
127 let refuse = |why: String| Err(PeerError::Protocol(format!("descriptor refused: {why}")));
128 if d.piece_size > cfg.max_piece_size {
129 return refuse(format!(
130 "piece size {} exceeds the accepted {}",
131 d.piece_size, cfg.max_piece_size
132 ));
133 }
134 if d.total_size > cfg.max_artifact_bytes {
135 return refuse(format!(
136 "total size {} exceeds the accepted {}",
137 d.total_size, cfg.max_artifact_bytes
138 ));
139 }
140 if let Some(max) = expect.max_size
141 && d.total_size > max
142 {
143 return refuse(format!("total size {} exceeds the expected maximum {max}", d.total_size));
144 }
145 if let Some(size) = expect.size
146 && d.total_size != size
147 {
148 return refuse(format!("total size {} is not the expected {size}", d.total_size));
149 }
150 if let Some(root) = expect.root
151 && d.id() != root
152 {
153 return refuse("artifact id is not the expected root".into());
154 }
155 Ok(())
156 }
157
158 pub(crate) async fn open_session_for_lookup(
159 &self,
160 dialer: &dyn PeerDialer,
161 peer: &str,
162 key: ArtifactKey,
163 ) -> Result<(Session, ArtifactDescriptor), String> {
164 self.open_session(dialer, peer, key, &Expect::default()).await
165 }
166
167 async fn open_session(
169 &self,
170 dialer: &dyn PeerDialer,
171 peer: &str,
172 key: ArtifactKey,
173 expect: &Expect,
174 ) -> Result<(Session, ArtifactDescriptor), String> {
175 let mut stream = tokio::time::timeout(self.config().recv_timeout, dialer.dial(peer))
176 .await
177 .map_err(|_| "dial timed out".to_string())?
178 .map_err(|e| e.to_string())?;
179 let result = self.handshake(stream.as_mut(), key, expect).await;
180 match result {
181 Ok((d, has)) => Ok((
182 Session {
183 peer: peer.to_string(),
184 stream,
185 has,
186 },
187 d,
188 )),
189 Err(e) => {
190 let _ = stream.close().await;
191 Err(e.to_string())
192 }
193 }
194 }
195
196 async fn handshake(
197 &self,
198 stream: &mut dyn MeshStream,
199 key: ArtifactKey,
200 expect: &Expect,
201 ) -> Result<(ArtifactDescriptor, Bitfield), PeerError> {
202 send(stream, &ArtifactMsg::MetaRequest { key }).await?;
203 let d = match self.recv(stream).await? {
204 ArtifactMsg::Meta { descriptor } => descriptor,
205 ArtifactMsg::Reject { reason, .. } => return Err(PeerError::Refused(reason)),
206 other => return Err(PeerError::Protocol(format!("unexpected {other:?}"))),
207 };
208 let matches_key = match key {
209 ArtifactKey::Root(id) => d.id() == id,
210 ArtifactKey::Content(h) => d.content_hash == h,
211 };
212 if !matches_key {
213 return Err(PeerError::Protocol(format!("descriptor does not match {key}")));
214 }
215 d.validate().map_err(PeerError::Wire)?;
216 self.check_descriptor(&d, expect)?;
217 let id = d.id();
218 let has = match self.recv(stream).await? {
219 ArtifactMsg::Announce { id: aid, have } if aid == id && have.len() == d.piece_count() => {
220 have
221 }
222 other => return Err(PeerError::Protocol(format!("unexpected {other:?}"))),
223 };
224 Ok((d, has))
225 }
226
227 pub async fn swarm_fetch(
238 self: &Arc<Self>,
239 dialer: Arc<dyn PeerDialer>,
240 candidates: Vec<PeerCandidate>,
241 key: ArtifactKey,
242 opts: &SwarmFetchOptions,
243 ) -> Result<FetchOutcome, FetchError> {
244 if let Some(d) = self.resolve(&key).filter(|d| self.is_complete(d)) {
245 return Ok(FetchOutcome {
246 id: d.id(),
247 descriptor: d,
248 pieces_fetched: 0,
249 bytes_fetched: 0,
250 pieces_rejected: 0,
251 sources: Vec::new(),
252 });
253 }
254 let ordered = order_peers(
255 candidates,
256 opts.local_lan.as_deref(),
257 &self.swarm.links,
258 &|p| self.is_banned(p),
259 );
260 let mut queue: VecDeque<PeerCandidate> = ordered.into();
261 let mut dialed = 0usize;
262 let mut last_unverified = None;
263 for _ in 0..MAX_ATTEMPTS {
264 match self.swarm_attempt(&dialer, &mut queue, key, opts, &mut dialed).await {
265 Ok(out) => return Ok(out),
266 Err(Attempt::Failed(e)) => {
267 return Err(match last_unverified {
268 Some(msg) => FetchError::Verification(msg),
269 None => e,
270 });
271 }
272 Err(Attempt::Unverified { error, peers }) => {
273 for p in &peers {
274 self.ban_peer(p, &format!("vouched for content that does not verify as {key}"));
275 }
276 last_unverified = Some(error);
277 }
278 }
279 }
280 let msg = last_unverified.unwrap_or_default();
281 self.chain_fetch(serde_json::json!({
282 "key": key.to_string(),
283 "result": "failed",
284 "error": msg,
285 "peers_dialed": dialed,
286 "swarm": true,
287 "node": self.node_id(),
288 }));
289 Err(FetchError::Verification(msg))
290 }
291
292 async fn swarm_attempt(
293 self: &Arc<Self>,
294 dialer: &Arc<dyn PeerDialer>,
295 queue: &mut VecDeque<PeerCandidate>,
296 key: ArtifactKey,
297 opts: &SwarmFetchOptions,
298 dialed: &mut usize,
299 ) -> Result<FetchOutcome, Attempt> {
300 let mut last_error = String::from("no candidate peers");
301
302 let mut first = None;
304 while let Some(c) = queue.pop_front() {
305 if self.is_banned(&c.peer_id) {
306 continue;
307 }
308 *dialed += 1;
309 match self.open_session(dialer.as_ref(), &c.peer_id, key, &opts.expect).await {
310 Ok(x) => {
311 first = Some(x);
312 break;
313 }
314 Err(e) => last_error = format!("{}: {e}", c.peer_id),
315 }
316 }
317 let Some((s0, d)) = first else {
318 return Err(Attempt::Failed(self.swarm_failed(
319 &key,
320 None,
321 &Totals::default(),
322 *dialed,
323 0,
324 last_error,
325 )));
326 };
327 let id = self.note_pending(&d).map_err(|e| Attempt::Failed(e.into()))?;
328 let mut session_peers = vec![s0.peer.clone()];
329 let mut picker = PiecePicker::new(self.have_of(&d));
330 picker.add_peer(&s0.peer, s0.has.clone());
331 let shared = Arc::new(Mutex::new(Shared {
332 picker,
333 totals: Totals::default(),
334 }));
335 let notify = Arc::new(Notify::new());
336 let max = self.config().max_sources;
337 let mut set: JoinSet<()> = JoinSet::new();
338 let mut active = 0usize;
339 let spawn = |set: &mut JoinSet<()>, s: Session| {
340 let (ex, sh, n, d) = (self.clone(), shared.clone(), notify.clone(), d.clone());
341 set.spawn(async move { run_worker(ex, sh, n, d, s).await });
342 };
343 spawn(&mut set, s0);
344 active += 1;
345
346 loop {
347 while active < max && !lock(&shared).picker.is_complete() {
349 let Some(c) = queue.pop_front() else { break };
350 if self.is_banned(&c.peer_id) {
351 continue;
352 }
353 *dialed += 1;
354 let expect = Expect {
355 root: Some(id),
356 ..opts.expect.clone()
357 };
358 match self.open_session(dialer.as_ref(), &c.peer_id, ArtifactKey::Root(id), &expect).await {
359 Ok((s, _)) => {
360 session_peers.push(s.peer.clone());
361 lock(&shared).picker.add_peer(&s.peer, s.has.clone());
362 spawn(&mut set, s);
363 active += 1;
364 }
365 Err(e) => {
366 last_error = format!("{}: {e}", c.peer_id);
367 lock(&shared).totals.lost.push((c.peer_id, e));
368 }
369 }
370 }
371 if set.join_next().await.is_none() {
372 break;
373 }
374 active -= 1;
375 if lock(&shared).picker.is_complete() {
376 break;
377 }
378 }
379 set.shutdown().await;
380
381 let g = lock(&shared);
382 let totals = &g.totals;
383 if !g.picker.is_complete() {
384 let missing = g.picker.missing();
385 return Err(Attempt::Failed(self.swarm_failed(
386 &key,
387 Some(&d),
388 totals,
389 *dialed,
390 missing,
391 last_error,
392 )));
393 }
394 if let Err(e) = self.promote(&d, &totals.written) {
395 return Err(Attempt::Unverified {
397 error: e.to_string(),
398 peers: session_peers,
399 });
400 }
401 self.materialize(&id).map_err(|e| Attempt::Failed(e.into()))?;
402 self.chain_fetch(serde_json::json!({
403 "artifact_id": id.to_string(),
404 "content_hash": d.content_hex(),
405 "source_peer": totals.sources.last().cloned().unwrap_or_default(),
406 "sources": totals.sources,
407 "bytes": totals.bytes,
408 "total_size": d.total_size,
409 "pieces_fetched": totals.pieces,
410 "pieces_rejected": totals.rejected,
411 "peers_dialed": *dialed,
412 "peers_lost": totals.lost.iter().map(|(p, w)| serde_json::json!({"peer": p, "why": w})).collect::<Vec<_>>(),
413 "peers_banned": totals.banned,
414 "result": "verified",
415 "swarm": true,
416 "node": self.node_id(),
417 }));
418 Ok(FetchOutcome {
419 id,
420 descriptor: d,
421 pieces_fetched: totals.pieces,
422 bytes_fetched: totals.bytes,
423 pieces_rejected: totals.rejected,
424 sources: totals.sources.clone(),
425 })
426 }
427
428 fn swarm_failed(
429 &self,
430 key: &ArtifactKey,
431 d: Option<&ArtifactDescriptor>,
432 totals: &Totals,
433 dialed: usize,
434 missing: u32,
435 last_error: String,
436 ) -> FetchError {
437 self.chain_fetch(serde_json::json!({
438 "key": key.to_string(),
439 "artifact_id": d.map(|d| d.id().to_string()),
440 "sources": totals.sources,
441 "bytes": totals.bytes,
442 "pieces_fetched": totals.pieces,
443 "pieces_rejected": totals.rejected,
444 "missing_pieces": missing,
445 "peers_dialed": dialed,
446 "peers_banned": totals.banned,
447 "result": "failed",
448 "error": last_error,
449 "swarm": true,
450 "node": self.node_id(),
451 }));
452 FetchError::Incomplete {
453 missing,
454 last_error,
455 }
456 }
457}
458
459async fn run_worker(
461 ex: Arc<ArtifactExchange>,
462 shared: Arc<Mutex<Shared>>,
463 notify: Arc<Notify>,
464 d: ArtifactDescriptor,
465 mut s: Session,
466) {
467 let id = d.id();
468 loop {
469 let wake = notify.notified();
472 tokio::pin!(wake);
473 wake.as_mut().enable();
474 let pick = {
475 let mut g = lock(&shared);
476 if g.picker.is_complete() {
477 break;
478 }
479 g.picker.pick(&s.peer)
480 };
481 let index = match pick {
482 Pick::Nothing => break,
483 Pick::Wait => {
484 wake.await;
485 continue;
486 }
487 Pick::Piece(i) => i,
488 };
489 let started = Instant::now();
490 let got = request_piece(&ex, s.stream.as_mut(), &d, index).await;
491 let mut g = lock(&shared);
492 match got {
493 Ok(Some(data)) => {
494 let hash = *blake3::hash(&data).as_bytes();
495 if hash != d.pieces[index as usize] {
496 g.totals.rejected += 1;
497 g.picker.release(index);
498 let banned = ex.report_corrupt_piece(&id, index, &s.peer);
499 if banned {
500 g.picker.remove_peer(&s.peer);
501 g.totals.banned.push(s.peer.clone());
502 notify.notify_waiters();
503 break;
504 }
505 notify.notify_waiters();
506 continue;
507 }
508 match ex.store_piece(&data, &hash) {
509 Ok(created) => {
510 if created {
511 g.totals.written.insert(hash);
512 }
513 }
514 Err(e) => {
515 g.picker.remove_peer(&s.peer);
516 g.totals.lost.push((s.peer.clone(), e.to_string()));
517 notify.notify_waiters();
518 break;
519 }
520 }
521 g.picker.complete(index);
522 g.totals.pieces += 1;
523 g.totals.bytes += data.len() as u64;
524 if !g.totals.sources.contains(&s.peer) {
525 g.totals.sources.push(s.peer.clone());
526 }
527 ex.swarm
528 .links
529 .record(&s.peer, data.len() as u64, started.elapsed().as_secs_f64());
530 notify.notify_waiters();
531 }
532 Ok(None) => {
533 g.picker.peer_lacks(&s.peer, index);
534 notify.notify_waiters();
535 }
536 Err(e) => {
537 g.picker.remove_peer(&s.peer);
538 g.totals.lost.push((s.peer.clone(), e.to_string()));
539 notify.notify_waiters();
540 break;
541 }
542 }
543 }
544 let _ = s.stream.close().await;
545 notify.notify_waiters();
547}
548
549async fn request_piece(
552 ex: &ArtifactExchange,
553 stream: &mut dyn MeshStream,
554 d: &ArtifactDescriptor,
555 index: u32,
556) -> Result<Option<Vec<u8>>, PeerError> {
557 let id = d.id();
558 send(
559 stream,
560 &ArtifactMsg::Request {
561 id,
562 pieces: vec![index],
563 },
564 )
565 .await?;
566 let want = d.piece_len(index) as usize;
567 let mut buf: Vec<u8> = Vec::new();
568 while buf.len() < want {
569 match ex.recv(stream).await? {
570 ArtifactMsg::Piece {
571 id: pid,
572 index: i,
573 offset,
574 data,
575 } if pid == id && i == index && offset == buf.len() as u64 => {
576 if buf.len() + data.len() > want {
577 return Err(PeerError::Protocol("piece longer than descriptor".into()));
578 }
579 ex.swarm.bandwidth.download(data.len()).await;
580 buf.extend_from_slice(&data);
581 }
582 ArtifactMsg::NoPiece { id: pid, index: i } if pid == id && i == index => {
583 return Ok(None);
584 }
585 ArtifactMsg::Reject { reason, .. } => return Err(PeerError::Refused(reason)),
586 other => return Err(PeerError::Protocol(format!("unexpected {other:?}"))),
587 }
588 }
589 Ok(Some(buf))
590}