use super::*;
use std::io::Read as _;
pub(crate) fn framed(pack: &[u8], sizes: &[usize]) -> Vec<u8> {
let mut reply = b"000dpackfile\n".to_vec();
let mut at = 0;
for size in sizes.iter().copied().chain(std::iter::repeat(PACKET_DATA)) {
if at == pack.len() {
break;
}
let end = (at + size).min(pack.len());
reply.extend(format!("{:04x}\u{1}", end - at + 5).as_bytes());
reply.extend_from_slice(&pack[at..end]);
at = end;
}
reply.extend_from_slice(b"0000");
reply
}
fn file_of(bytes: &[u8]) -> File {
let mut file = tempfile::tempfile().unwrap();
file.write_all(bytes).unwrap();
file
}
#[test]
fn a_reply_not_in_whole_packets_is_refused() {
let pack: Vec<u8> = (0..3 * PACKET_DATA as u32).map(|n| n as u8).collect();
let whole = framed(&pack, &[]);
let format = ObjectFormat::SHA1;
let laid = Framed::of(&file_of(&whole), format).unwrap();
assert_eq!(laid.body(), pack.len() as u64 - 20);
for cut in [1, 3, 4, 5, 6, 9] {
let short = &whole[..whole.len() - cut];
assert!(Framed::of(&file_of(short), format).is_err(), "{cut} short");
}
assert!(Framed::of(&file_of(&whole[..13 + 7]), format).is_err());
for stub in 1..=5 {
let mut short = whole[..13 + 2 * (PACKET_DATA + 5) + stub].to_vec();
short.extend(b"0000");
assert!(
Framed::of(&file_of(&short), format).is_err(),
"{stub} bytes"
);
}
let long = framed(&pack[..PACKET_DATA + 1], &[PACKET_DATA + 1]);
assert!(Framed::of(&file_of(&long), format).is_err());
let first = framed(&pack, &[0x7535 - 5, 0x8abb - 5]);
assert!(Framed::of(&file_of(&first), format).is_err());
let middle = framed(&pack, &[PACKET_DATA, 0x7535 - 5, 0x8abb - 5]);
let file = file_of(&middle);
let laid = Framed::of(&file, format).unwrap();
assert!(laid.stream_ahead(&file, &mut |_| {}).is_err());
assert!(
laid.read(&file, PACKET_DATA as u64..PACKET_DATA as u64 + 1)
.is_err()
);
}
#[test]
fn a_hash_made_a_block_at_a_time_matches_and_resumes() {
let mut seed = 7u64;
let mut byte = || {
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(seed >> 33) as u8
};
let lengths = [0, 3, 55, 56, 63, 64, 65, 119, 120, 128, 1000, 65_536 + 17];
for format in [ObjectFormat::SHA1, ObjectFormat::SHA256] {
for len in lengths {
let message: Vec<u8> = (0..len).map(|_| byte()).collect();
let expected = match format == ObjectFormat::SHA1 {
true => <sha1::Sha1 as sha1::Digest>::digest(&message).to_vec(),
false => <sha2::Sha256 as sha2::Digest>::digest(&message).to_vec(),
};
for piece in [1, 7, 64, 100, len.max(1)] {
let mut hasher = Hasher::new(format);
message.chunks(piece).for_each(|bytes| hasher.update(bytes));
assert_eq!(hasher.finish(), expected, "{format:?} {len} in {piece}s");
}
for split in [0, len / 2, len] {
let mut hasher = Hasher::new(format);
hasher.update(&message[..split]);
let mut resumed = Hasher::resume(hasher.state, hasher.blocks);
resumed.update(&message[hasher.blocks as usize * 64..]);
assert_eq!(resumed.finish(), expected, "{format:?} {len} from {split}");
}
}
}
let mut abc = Hasher::new(ObjectFormat::SHA256);
abc.update(b"abc");
let hex = knot_types::lowercase_hex(&abc.finish());
assert_eq!(
hex,
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn a_grown_pack_checksums_whole_however_long_the_prior() {
let digest = |format: ObjectFormat, bytes: &[u8]| match format == ObjectFormat::SHA1 {
true => <sha1::Sha1 as sha1::Digest>::digest(bytes).to_vec(),
false => <sha2::Sha256 as sha2::Digest>::digest(bytes).to_vec(),
};
let mut seed = 7u64;
let mut pack = |count: u32, body: usize, format: ObjectFormat| {
let mut pack = pack_header(count);
pack.extend((12..body).map(|_| {
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(seed >> 33) as u8
}));
pack.extend(digest(format, &pack));
pack
};
let mut bodies = vec![12, 40, 63, 64, 65, 119, 120, 127, 128, 129, 1000];
for packet in [PACKET_DATA, 2 * PACKET_DATA] {
for off in [-21i64, -20, -19, -10, -1, 0, 1, 64] {
bodies.push((packet as i64 + off) as usize);
}
}
for format in [ObjectFormat::SHA1, ObjectFormat::SHA256] {
let hash_len = format.kind().len_in_bytes();
for body in bodies.iter().copied() {
let prior = pack(5, body, format);
let file = file_of(&framed(&prior, &[]));
for added in [1, 4, 12] {
let segment = pack(added, 12 + 70 * added as usize, format);
let mut whole = prior[..body].to_vec();
whole[8..12].copy_from_slice(&(5 + added).to_be_bytes());
whole.extend_from_slice(&segment[12..segment.len() - hash_len]);
let expected = digest(format, &whole);
let mut grown = tempfile::tempfile().unwrap();
(&file).seek(SeekFrom::Start(0)).unwrap();
reflink_or_copy(&file, &grown).unwrap();
let made = extend(&file, &mut grown, &segment, format, Checksum::Hash);
assert_eq!(made.unwrap(), expected, "{format:?} {body} +{added}");
whole.extend(&expected);
let mut held = Vec::new();
grown.seek(SeekFrom::Start(0)).unwrap();
grown.read_to_end(&mut held).unwrap();
assert!(held == framed(&whole, &[]), "{format:?} {body} +{added}");
}
}
}
}
#[test]
fn a_reply_grown_without_states_hashes_what_it_holds() {
let mut x = 0x9e37_79b9_7f4a_7c15u64;
let noise: Vec<u8> = (0..3 * CHUNK + 4321)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x as u8
})
.collect();
for format in [ObjectFormat::SHA1, ObjectFormat::SHA256] {
let hash = |bytes: &[u8]| {
let mut hasher = Hasher::new(format);
hasher.update(bytes);
hasher.finish()
};
let mut pack = pack_header(5);
pack.extend_from_slice(&noise);
pack.extend(hash(&pack));
let prior = file_of(&framed(&pack, &[]));
let laid = Framed::of(&prior, format).unwrap();
let body = laid.body() as usize;
let mut streamed = Hasher::new(format);
laid.stream_ahead(&prior, &mut |bytes| streamed.update(bytes))
.unwrap();
assert_eq!(streamed.finish(), hash(&pack[..body]));
let data = PACKET_DATA as u64;
let tails = [12, 63, 64, 128, data, 1024 * 64, CHUNK as u64 - 13];
for (kept, tail) in [body as u64, body as u64 - 1000]
.into_iter()
.flat_map(|kept| {
tails
.into_iter()
.chain([kept - 900])
.map(move |tail| (kept, tail))
})
{
let mut reply = tempfile::tempfile().unwrap();
(&prior).seek(SeekFrom::Start(0)).unwrap();
reflink_or_copy(&prior, &reply).unwrap();
let entries = b"two entries";
let rewrite = Rewrite {
kept,
dropped: 1,
added: 3,
entries,
mark: Some(kept + 4),
tail_mark: Some(tail),
};
let grown = replace_tail(&prior, &mut reply, rewrite, format, Checksum::Hash).unwrap();
let mut whole = pack[..kept as usize].to_vec();
whole[8..12].copy_from_slice(&7u32.to_be_bytes());
whole.extend_from_slice(entries);
let case = format!("{format:?} kept {kept} tail {tail}");
assert_eq!(grown.checksum, hash(&whole), "{case}");
for (mark, state) in [(kept + 4, grown.at_mark), (tail, grown.at_tail)] {
let blocks = mark / 64;
let mut resumed = Hasher::resume(state.unwrap(), blocks);
resumed.update(&whole[blocks as usize * 64..]);
assert_eq!(resumed.finish(), grown.checksum, "{case} from {mark}");
}
whole.extend(&grown.checksum);
let mut held = Vec::new();
reply.seek(SeekFrom::Start(0)).unwrap();
reply.read_to_end(&mut held).unwrap();
assert!(held == framed(&whole, &[]), "{case}");
}
}
}