diff --git a/src/directory.rs b/src/directory.rs index 672ce3de..2a2608a8 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -58,9 +58,13 @@ impl Directory { pub(crate) fn fingerprint(&self) -> Hash { let mut hasher = FingerprintHasher::new(FingerprintPrefix::Directory); - for (component, entry) in &self.entries { - hasher.field(0, entry.fingerprint(component).as_bytes()); - } + let entries = self + .entries + .iter() + .flat_map(|(component, entry)| *entry.fingerprint(component).as_bytes()) + .collect::>(); + + hasher.field(0, &entries); hasher.finalize() } diff --git a/src/fingerprint_hasher.rs b/src/fingerprint_hasher.rs index 69b75108..6a0d1c5c 100644 --- a/src/fingerprint_hasher.rs +++ b/src/fingerprint_hasher.rs @@ -2,15 +2,15 @@ use super::*; pub(crate) struct FingerprintHasher { hasher: Hasher, - tag: u64, + next: u64, } impl FingerprintHasher { pub(crate) fn field(&mut self, tag: u64, field: &[u8]) { - assert!(tag >= self.tag, "unexpected tag {tag}"); - self.tag = tag; - self.hasher.update(&tag.to_le_bytes()); - self.hasher.update(&field.len().into_u64().to_le_bytes()); + assert!(tag == self.next, "unexpected tag {tag}"); + self.next += 1; + self.varint(tag); + self.varint(field.len().into_u64()); self.hasher.update(field); } @@ -19,11 +19,26 @@ impl FingerprintHasher { } pub(crate) fn new(context: FingerprintPrefix) -> Self { - let mut hasher = Hasher::new(); + let mut hasher = Self { + hasher: Hasher::new(), + next: 0, + }; let prefix = context.prefix(); - hasher.update(&prefix.len().into_u64().to_le_bytes()); - hasher.update(prefix.as_bytes()); - Self { hasher, tag: 0 } + hasher.varint(prefix.len().into_u64()); + hasher.hasher.update(prefix.as_bytes()); + hasher + } + + fn varint(&mut self, mut n: u64) { + loop { + let byte = (n & 0b0111_1111).try_into().unwrap(); + n >>= 7; + if n == 0 { + self.hasher.update(&[byte]); + break; + } + self.hasher.update(&[byte | 0b1000_0000]); + } } } @@ -50,10 +65,44 @@ mod tests { } #[test] - #[should_panic(expected = "unexpected tag 0")] + #[should_panic(expected = "unexpected tag 2")] fn tag_order() { let mut hasher = FingerprintHasher::new(FingerprintPrefix::File); - hasher.field(1, &[]); hasher.field(0, &[]); + hasher.field(2, &[]); + } + + #[test] + fn varint_encoding() { + #[track_caller] + fn case(len: usize, varint: &[u8]) { + let field = iter::repeat_n(0, len).collect::>(); + + let actual = { + let mut hasher = FingerprintHasher::new(FingerprintPrefix::File); + hasher.field(0, &field); + hasher.finalize() + }; + + let expected = { + let mut hasher = Hasher::new(); + hasher.update(&[13]); + hasher.update("filepack:file".as_bytes()); + hasher.update(&[0]); + hasher.update(varint); + hasher.update(&field); + hasher.finalize().into() + }; + + assert_eq!(actual, expected, "unexpected hash for length {len} field"); + } + + case(0, &[0]); + case(1, &[1]); + case(127, &[0x7F]); + case(128, &[0x80, 0x01]); + case(129, &[0x81, 0x01]); + case(16383, &[0xFF, 0x7F]); + case(16384, &[0x80, 0x80, 0x01]); } } diff --git a/src/test.rs b/src/test.rs index fc0a8cd1..66d3aceb 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,8 +15,8 @@ pub(crate) const PUBLIC_KEY: &str = pub(crate) const SIGNATURE: &str = concat!( "signature1a67dndhhmae7p6fsfnj0z37zf78cde6mwqgtms0y87h8ldlvvflyq4uf5nw04lxs6dgzqf", - "h4rdhxffxdukfwf4hq39d7vn2fu4eqlxf3q5j9kf2jslrnmfptk2rsj85tnp4ttqwagu46kkw64uf3dg", - "ffz3juhjnh9us86m2xzugrgxhn87kcn6azkernfruce7qh4mhzfefycuqq7t9j5l" + "h4rdhxffxdukfwf4hq39d7vn2fu4eqlxf3qsvczv268s6lkxtsc0eufqc5xz3g99640gtpwadk349d8f", + "qkjgl3tkp2m95ujz9arxzwt74ggzd3f9vnc6skcns9kn6xnxuqz6v26yrgw3lv6u", ); pub(crate) const WEAK_PUBLIC_KEY: &str = diff --git a/tests/fingerprint.rs b/tests/fingerprint.rs index ef24a879..e832fda9 100644 --- a/tests/fingerprint.rs +++ b/tests/fingerprint.rs @@ -19,7 +19,7 @@ fn fingerprint() { ) .success(); - let fingerprint = "package1ase89zy0tuschqfzg6ltu87devt2kt8mkr76zsuzf65kkxa4ycg8q0kds50"; + let fingerprint = "package1a6mpecnnzja3uzmdxruf87074wy778qra3yn25xuudzgjx49v3tsq9qx6vs"; let path = test.path(); diff --git a/tests/verify.rs b/tests/verify.rs index b49cd903..4e29d4ae 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -663,7 +663,7 @@ fn verify_fingerprint() { .args([ "verify", "--fingerprint", - "package1ase89zy0tuschqfzg6ltu87devt2kt8mkr76zsuzf65kkxa4ycg8q0kds50", + "package1a6mpecnnzja3uzmdxruf87074wy778qra3yn25xuudzgjx49v3tsq9qx6vs", ]) .stderr("successfully verified 1 file totaling 0 bytes\n") .success() @@ -676,7 +676,7 @@ fn verify_fingerprint() { "\ fingerprint mismatch: `.*filepack\\.json` expected: package1a4uf5nw04lxs6dgzqfh4rdhxffxdukfwf4hq39d7vn2fu4eqlxf3ql7ykr3 - actual: package1ase89zy0tuschqfzg6ltu87devt2kt8mkr76zsuzf65kkxa4ycg8q0kds50 + actual: package1a6mpecnnzja3uzmdxruf87074wy778qra3yn25xuudzgjx49v3tsq9qx6vs error: fingerprint mismatch\n", ) .failure();