use super::*;
use crate::mesh_artifact_wire::MAX_ARTIFACT_FRAME;
fn exchange(piece: u64) -> ArtifactExchange {
let cfg = ExchangeConfig {
piece_size: piece,
..Default::default()
};
ArtifactExchange::new("n1", Arc::new(ArtifactStore::new_memory()), cfg).unwrap()
}
#[test]
fn seeding_splits_into_pieces_and_roundtrips() {
let x = exchange(1024);
let data: Vec<u8> = (0..5000u32).map(|i| (i % 251) as u8).collect();
let d = x.seed_bytes(&data).unwrap();
assert_eq!(d.piece_count(), 5);
assert_eq!(d.piece_len(4), 5000 - 4096);
assert_eq!(d.content_hash, *blake3::hash(&data).as_bytes());
assert!(x.have(&d.id()).unwrap().is_complete());
assert_eq!(x.read_all(&d.id()).unwrap(), data);
assert_eq!(
x.resolve(&ArtifactKey::Content(d.content_hash))
.unwrap()
.id(),
d.id()
);
}
#[test]
fn have_follows_the_store() {
let x = exchange(1024);
let d = x.seed_bytes(&[3u8; 3000]).unwrap();
x.store().remove(&hex_encode(&d.pieces[2])).unwrap();
let have = x.have(&d.id()).unwrap();
assert_eq!(have.missing().collect::<Vec<_>>(), vec![2]);
assert!(x.read_all(&d.id()).is_err());
}
#[test]
fn seeding_with_wrong_expectation_registers_nothing() {
let x = exchange(1024);
let err = x.seed_reader(&mut &b"abc"[..], Some(([0; 32], 3)));
assert!(matches!(err, Err(ExchangeError::Mismatch(_))));
assert!(
x.resolve(&ArtifactKey::Content(*blake3::hash(b"abc").as_bytes()))
.is_none()
);
}
#[test]
fn nothing_is_servable_without_a_grant() {
let x = exchange(1024);
let d = x.seed_bytes(b"payload").unwrap();
assert!(!x.is_servable(&d));
x.grant(d.content_hash, "pkg");
assert!(x.is_servable(&d));
}
#[test]
fn config_limits_are_enforced() {
let store = Arc::new(ArtifactStore::new_memory());
let bad_block = ExchangeConfig {
block_size: MAX_ARTIFACT_FRAME,
..Default::default()
};
assert!(ArtifactExchange::new("n", store.clone(), bad_block).is_err());
let bad_piece = ExchangeConfig {
piece_size: 10,
..Default::default()
};
assert!(ArtifactExchange::new("n", store, bad_piece).is_err());
}
fn liar(claimed: [u8; 32], junk: &[u8]) -> ArtifactDescriptor {
ArtifactDescriptor {
piece_size: 1024,
total_size: junk.len() as u64,
content_hash: claimed,
pieces: vec![*blake3::hash(junk).as_bytes()],
}
}
#[test]
fn pending_descriptors_are_neither_resolvable_by_content_nor_servable() {
let x = exchange(1024);
let claimed = *blake3::hash(b"the real thing").as_bytes();
x.grant(claimed, "pkg");
let junk = [0xAA; 1024];
let d = liar(claimed, &junk);
x.note_pending(&d).unwrap();
x.store_piece(&junk, &d.pieces[0]).unwrap();
assert!(x.have(&d.id()).unwrap().is_complete());
assert!(x.resolve(&ArtifactKey::Content(claimed)).is_none());
assert!(x.resolve(&ArtifactKey::Root(d.id())).is_none());
assert!(!x.is_servable(&d));
}
#[test]
fn failed_promotion_discards_the_descriptor_and_unshared_pieces() {
let x = exchange(1024);
let honest = x.seed_bytes(&[7u8; 1024]).unwrap();
let junk = [0xAA; 1024];
let mut d = liar(honest.content_hash, &junk);
d.pieces.push(honest.pieces[0]);
d.total_size = 2048;
x.note_pending(&d).unwrap();
assert!(x.store_piece(&junk, &d.pieces[0]).unwrap(), "newly written");
let written = HashSet::from([d.pieces[0]]);
assert!(matches!(
x.promote(&d, &written),
Err(ExchangeError::Mismatch(_))
));
assert!(x.have(&d.id()).is_none(), "pending descriptor dropped");
assert!(
!x.store().contains(&hex_encode(&d.pieces[0])),
"junk piece dropped"
);
assert!(x.have(&honest.id()).unwrap().is_complete());
let key = ArtifactKey::Content(honest.content_hash);
assert_eq!(x.resolve(&key).unwrap().id(), honest.id());
}
#[test]
fn a_peer_descriptor_cannot_replace_a_verified_one() {
let x = exchange(1024);
let honest = x.seed_bytes(&[5u8; 2000]).unwrap();
let forged = ArtifactDescriptor {
content_hash: [1; 32],
..honest.clone()
};
assert_ne!(forged.id(), honest.id());
x.note_pending(&forged).unwrap();
assert!(x.promote(&forged, &HashSet::new()).is_err());
assert_eq!(x.descriptor(&honest.id()).unwrap(), honest);
assert!(x.have(&honest.id()).unwrap().is_complete());
}
#[test]
fn failed_promotion_never_removes_blobs_it_did_not_write() {
let x = exchange(1024);
let blob = [0x42u8; 900];
let k = x.store().store(&blob, ArtifactType::Generic).unwrap();
let d = liar([8; 32], &blob);
x.note_pending(&d).unwrap();
assert!(!x.store_piece(&blob, &d.pieces[0]).unwrap(), "already held");
assert!(x.promote(&d, &HashSet::new()).is_err());
assert!(x.have(&d.id()).is_none(), "pending descriptor dropped");
assert!(x.store().contains(&k), "pre-existing blob kept");
}