diff --git a/gix-hash/Cargo.toml b/gix-hash/Cargo.toml index 6b970cf4a72..00b46d8eae1 100644 --- a/gix-hash/Cargo.toml +++ b/gix-hash/Cargo.toml @@ -19,6 +19,8 @@ test = false default = ["sha1"] ## Support for SHA-1 digests. sha1 = [] +## Support for SHA256 digests. +sha256 = [] ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde = ["dep:serde", "faster-hex/serde"] diff --git a/gix-hash/src/hasher.rs b/gix-hash/src/hasher.rs index 4519f599e45..4af6112a4ab 100644 --- a/gix-hash/src/hasher.rs +++ b/gix-hash/src/hasher.rs @@ -67,6 +67,8 @@ pub(super) mod _impl { pub fn hasher(kind: crate::Kind) -> Hasher { match kind { crate::Kind::Sha1 => Hasher::default(), + #[cfg(feature = "sha256")] + crate::Kind::Sha256 => Hasher::default(), } } } diff --git a/gix-hash/src/io.rs b/gix-hash/src/io.rs index 19e981453dc..7e681d3dbfe 100644 --- a/gix-hash/src/io.rs +++ b/gix-hash/src/io.rs @@ -118,6 +118,11 @@ pub(super) mod _impl { inner, hash: crate::hasher(object_hash), }, + #[cfg(feature = "sha256")] + crate::Kind::Sha256 => Write { + inner, + hash: crate::hasher(object_hash), + }, } } } diff --git a/gix-hash/src/kind.rs b/gix-hash/src/kind.rs index de787b417fd..362b6cba71c 100644 --- a/gix-hash/src/kind.rs +++ b/gix-hash/src/kind.rs @@ -1,6 +1,9 @@ use std::str::FromStr; -use crate::{oid, Kind, ObjectId}; +use crate::{oid, Kind, ObjectId, SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; + +#[cfg(feature = "sha256")] +use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; impl TryFrom for Kind { type Error = u8; @@ -8,6 +11,8 @@ impl TryFrom for Kind { fn try_from(value: u8) -> Result { Ok(match value { 1 => Kind::Sha1, + #[cfg(feature = "sha256")] + 2 => Kind::Sha256, unknown => return Err(unknown), }) } @@ -19,6 +24,8 @@ impl FromStr for Kind { fn from_str(s: &str) -> Result { Ok(match s { "sha1" | "SHA1" => Kind::Sha1, + #[cfg(feature = "sha256")] + "sha256" | "SHA256" => Kind::Sha256, other => return Err(other.into()), }) } @@ -28,6 +35,8 @@ impl std::fmt::Display for Kind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Kind::Sha1 => f.write_str("SHA1"), + #[cfg(feature = "sha256")] + Kind::Sha256 => f.write_str("SHA256"), } } } @@ -36,13 +45,27 @@ impl Kind { /// Returns the shortest hash we support. #[inline] pub const fn shortest() -> Self { - Self::Sha1 + #[cfg(all(not(feature = "sha1"), feature = "sha256"))] + { + Self::Sha256 + } + #[cfg(feature = "sha1")] + { + Self::Sha1 + } } /// Returns the longest hash we support. #[inline] pub const fn longest() -> Self { - Self::Sha1 + #[cfg(feature = "sha256")] + { + Self::Sha256 + } + #[cfg(all(not(feature = "sha256"), feature = "sha1"))] + { + Self::Sha1 + } } /// Returns a buffer suitable to hold the longest possible hash in hex. @@ -61,14 +84,19 @@ impl Kind { #[inline] pub const fn len_in_hex(&self) -> usize { match self { - Kind::Sha1 => 40, + Kind::Sha1 => SIZE_OF_SHA1_HEX_DIGEST, + #[cfg(feature = "sha256")] + Kind::Sha256 => SIZE_OF_SHA256_HEX_DIGEST, } } + /// Returns the amount of bytes taken up by the hash of this instance. #[inline] pub const fn len_in_bytes(&self) -> usize { match self { - Kind::Sha1 => 20, + Kind::Sha1 => SIZE_OF_SHA1_DIGEST, + #[cfg(feature = "sha256")] + Kind::Sha256 => SIZE_OF_SHA256_DIGEST, } } @@ -77,7 +105,9 @@ impl Kind { #[inline] pub const fn from_hex_len(hex_len: usize) -> Option { Some(match hex_len { - 0..=40 => Kind::Sha1, + 0..=SIZE_OF_SHA1_HEX_DIGEST => Kind::Sha1, + #[cfg(feature = "sha256")] + 0..=SIZE_OF_SHA256_HEX_DIGEST => Kind::Sha256, _ => return None, }) } @@ -93,7 +123,9 @@ impl Kind { #[inline] pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { match bytes { - 20 => Kind::Sha1, + SIZE_OF_SHA1_DIGEST => Kind::Sha1, + #[cfg(feature = "sha256")] + SIZE_OF_SHA256_DIGEST => Kind::Sha256, _ => panic!("BUG: must be called only with valid hash lengths produced by len_in_bytes()"), } } @@ -103,6 +135,8 @@ impl Kind { pub fn null_ref(&self) -> &'static oid { match self { Kind::Sha1 => oid::null_sha1(), + #[cfg(feature = "sha256")] + Kind::Sha256 => oid::null_sha256(), } } @@ -111,6 +145,8 @@ impl Kind { pub const fn null(&self) -> ObjectId { match self { Kind::Sha1 => ObjectId::null_sha1(), + #[cfg(feature = "sha256")] + Kind::Sha256 => ObjectId::null_sha256(), } } diff --git a/gix-hash/src/lib.rs b/gix-hash/src/lib.rs index 02e7bc2420c..8e6f014c486 100644 --- a/gix-hash/src/lib.rs +++ b/gix-hash/src/lib.rs @@ -48,6 +48,25 @@ pub struct Prefix { /// The size of a SHA1 hash digest in bytes. const SIZE_OF_SHA1_DIGEST: usize = 20; +/// The size of a SHA1 hash digest in hex. +const SIZE_OF_SHA1_HEX_DIGEST: usize = 2 * SIZE_OF_SHA1_DIGEST; + +/// The size of a SHA256 hash digest in bytes. +#[cfg(feature = "sha256")] +const SIZE_OF_SHA256_DIGEST: usize = 32; +/// The size of a SHA256 hash digest in hex. +#[cfg(feature = "sha256")] +const SIZE_OF_SHA256_HEX_DIGEST: usize = 2 * SIZE_OF_SHA256_DIGEST; + +const EMPTY_BLOB_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = + b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"; +const EMPTY_TREE_SHA1: &[u8; SIZE_OF_SHA1_DIGEST] = + b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"; + +#[cfg(feature = "sha256")] +const EMPTY_BLOB_SHA256: &[u8; SIZE_OF_SHA256_DIGEST] = b"\x47\x3a\x0f\x4c\x3b\xe8\xa9\x36\x81\xa2\x67\xe3\xb1\xe9\xa7\xdc\xda\x11\x85\x43\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72\x18\x13"; +#[cfg(feature = "sha256")] +const EMPTY_TREE_SHA256: &[u8; SIZE_OF_SHA256_DIGEST] = b"\x6e\xf1\x9b\x41\x22\x5c\x53\x69\xf1\xc1\x04\xd4\x5d\x8d\x85\xef\xa9\xb0\x57\xb5\x3b\x14\xb4\xb9\xb9\x39\xdd\x74\xde\xcc\x53\x21"; /// Denotes the kind of function to produce a [`ObjectId`]. #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] @@ -57,6 +76,9 @@ pub enum Kind { /// The Sha1 hash with 160 bits. #[default] Sha1 = 1, + /// The Sha256 hash with 256 bits. + #[cfg(feature = "sha256")] + Sha256 = 2, } mod kind; diff --git a/gix-hash/src/object_id.rs b/gix-hash/src/object_id.rs index a2deb178294..e2f8a8d20ac 100644 --- a/gix-hash/src/object_id.rs +++ b/gix-hash/src/object_id.rs @@ -4,7 +4,10 @@ use std::{ ops::Deref, }; -use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; +use crate::{borrowed::oid, Kind, EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; + +#[cfg(feature = "sha256")] +use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; /// An owned hash identifying objects, most commonly `Sha1` #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] @@ -13,6 +16,9 @@ use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; pub enum ObjectId { /// A SHA 1 hash digest Sha1([u8; SIZE_OF_SHA1_DIGEST]), + /// A SHA 256 hash digest + #[cfg(feature = "sha256")] + Sha256([u8; SIZE_OF_SHA256_DIGEST]), } // False positive: https://github.com/rust-lang/rust-clippy/issues/2627 @@ -32,7 +38,10 @@ impl Hash for ObjectId { pub mod decode { use std::str::FromStr; - use crate::object_id::ObjectId; + use crate::{object_id::ObjectId, SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST}; + + #[cfg(feature = "sha256")] + use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST}; /// An error returned by [`ObjectId::from_hex()`][crate::ObjectId::from_hex()] #[derive(Debug, thiserror::Error)] @@ -51,9 +60,22 @@ pub mod decode { /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] pub fn from_hex(buffer: &[u8]) -> Result { match buffer.len() { - 40 => Ok({ + SIZE_OF_SHA1_HEX_DIGEST => Ok({ ObjectId::Sha1({ - let mut buf = [0; 20]; + let mut buf = [0; SIZE_OF_SHA1_DIGEST]; + faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err { + faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid, + faster_hex::Error::InvalidLength(_) => { + unreachable!("BUG: This is already checked") + } + })?; + buf + }) + }), + #[cfg(feature = "sha256")] + SIZE_OF_SHA256_HEX_DIGEST => Ok({ + ObjectId::Sha256({ + let mut buf = [0; SIZE_OF_SHA256_DIGEST]; faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err { faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid, faster_hex::Error::InvalidLength(_) => { @@ -84,6 +106,8 @@ impl ObjectId { pub fn kind(&self) -> Kind { match self { ObjectId::Sha1(_) => Kind::Sha1, + #[cfg(feature = "sha256")] + ObjectId::Sha256(_) => Kind::Sha256, } } /// Return the raw byte slice representing this hash. @@ -91,6 +115,8 @@ impl ObjectId { pub fn as_slice(&self) -> &[u8] { match self { Self::Sha1(b) => b.as_ref(), + #[cfg(feature = "sha256")] + Self::Sha256(b) => b.as_ref(), } } /// Return the raw mutable byte slice representing this hash. @@ -98,6 +124,8 @@ impl ObjectId { pub fn as_mut_slice(&mut self) -> &mut [u8] { match self { Self::Sha1(b) => b.as_mut(), + #[cfg(feature = "sha256")] + Self::Sha256(b) => b.as_mut(), } } @@ -105,9 +133,9 @@ impl ObjectId { #[inline] pub const fn empty_blob(hash: Kind) -> ObjectId { match hash { - Kind::Sha1 => { - ObjectId::Sha1(*b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91") - } + Kind::Sha1 => ObjectId::Sha1(*EMPTY_BLOB_SHA1), + #[cfg(feature = "sha256")] + Kind::Sha256 => ObjectId::Sha256(*EMPTY_BLOB_SHA256), } } @@ -115,9 +143,9 @@ impl ObjectId { #[inline] pub const fn empty_tree(hash: Kind) -> ObjectId { match hash { - Kind::Sha1 => { - ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04") - } + Kind::Sha1 => ObjectId::Sha1(*EMPTY_TREE_SHA1), + #[cfg(feature = "sha256")] + Kind::Sha256 => ObjectId::Sha256(*EMPTY_TREE_SHA256), } } @@ -127,6 +155,8 @@ impl ObjectId { pub const fn null(kind: Kind) -> ObjectId { match kind { Kind::Sha1 => Self::null_sha1(), + #[cfg(feature = "sha256")] + Kind::Sha256 => Self::null_sha256(), } } @@ -136,6 +166,8 @@ impl ObjectId { pub fn is_null(&self) -> bool { match self { ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(), + #[cfg(feature = "sha256")] + ObjectId::Sha256(digest) => &digest[..] == oid::null_sha256().as_bytes(), } } @@ -159,7 +191,7 @@ impl ObjectId { /// Use `Self::try_from(bytes)` for a fallible version. pub fn from_bytes_or_panic(bytes: &[u8]) -> Self { match bytes.len() { - 20 => Self::Sha1(bytes.try_into().expect("prior length validation")), + SIZE_OF_SHA1_DIGEST => Self::Sha1(bytes.try_into().expect("prior length validation")), other => panic!("BUG: unsupported hash len: {other}"), } } @@ -167,15 +199,15 @@ impl ObjectId { /// Sha1 hash specific methods impl ObjectId { - /// Instantiate an Digest from 20 bytes of a Sha1 digest. + /// Instantiate a Digest from 20 bytes of a Sha1 digest. #[inline] fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { ObjectId::Sha1(id) } - /// Instantiate an Digest from a slice 20 borrowed bytes of a Sha1 digest. + /// Instantiate a Digest from a slice 20 borrowed bytes of a Sha1 digest. /// - /// Panics of the slice doesn't have a length of 20. + /// Panics if the slice doesn't have a length of 20. #[inline] pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId { let mut id = [0; SIZE_OF_SHA1_DIGEST]; @@ -183,10 +215,28 @@ impl ObjectId { ObjectId::Sha1(id) } - /// Returns an Digest representing a Sha1 with whose memory is zeroed. + /// Instantiate a Digest from a slice 32 borrowed bytes of a Sha256 digest. + /// + /// Panics if the slice doesn't have a length of 32. + #[inline] + #[cfg(feature = "sha256")] + pub(crate) fn from_32_bytes(b: &[u8]) -> ObjectId { + let mut id = [0; SIZE_OF_SHA256_DIGEST]; + id.copy_from_slice(b); + ObjectId::Sha256(id) + } + + /// Returns a Digest representing a Sha1 whose memory is zeroed. #[inline] pub(crate) const fn null_sha1() -> ObjectId { - ObjectId::Sha1([0u8; 20]) + ObjectId::Sha1([0u8; SIZE_OF_SHA1_DIGEST]) + } + + /// Returns a Digest representing a Sha256 whose memory is zeroed. + #[inline] + #[cfg(feature = "sha256")] + pub(crate) const fn null_sha256() -> ObjectId { + ObjectId::Sha256([0u8; SIZE_OF_SHA256_DIGEST]) } } @@ -194,6 +244,8 @@ impl std::fmt::Debug for ObjectId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, + #[cfg(feature = "sha256")] + ObjectId::Sha256(_) => f.write_str("Sha256(")?, } for b in self.as_bytes() { write!(f, "{b:02x}")?; @@ -203,7 +255,7 @@ impl std::fmt::Debug for ObjectId { } impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { - fn from(v: [u8; 20]) -> Self { + fn from(v: [u8; SIZE_OF_SHA1_DIGEST]) -> Self { Self::new_sha1(v) } } @@ -212,6 +264,8 @@ impl From<&oid> for ObjectId { fn from(v: &oid) -> Self { match v.kind() { Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()), + #[cfg(feature = "sha256")] + Kind::Sha256 => ObjectId::from_32_bytes(v.as_bytes()), } } } diff --git a/gix-hash/src/oid.rs b/gix-hash/src/oid.rs index 0d952b219b0..a67463ebae6 100644 --- a/gix-hash/src/oid.rs +++ b/gix-hash/src/oid.rs @@ -1,6 +1,9 @@ use std::hash; -use crate::{Kind, ObjectId, SIZE_OF_SHA1_DIGEST}; +use crate::{Kind, ObjectId, EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST}; + +#[cfg(feature = "sha256")] +use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST}; /// A borrowed reference to a hash identifying objects. /// @@ -57,6 +60,8 @@ impl std::fmt::Debug for oid { "{}({})", match self.kind() { Kind::Sha1 => "Sha1", + #[cfg(feature = "sha256")] + Kind::Sha256 => "Sha256", }, self.to_hex(), ) @@ -77,7 +82,14 @@ impl oid { #[inline] pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> { match digest.len() { - 20 => Ok( + SIZE_OF_SHA1_DIGEST => Ok( + #[allow(unsafe_code)] + unsafe { + &*(std::ptr::from_ref::<[u8]>(digest) as *const oid) + }, + ), + #[cfg(feature = "sha256")] + SIZE_OF_SHA256_DIGEST => Ok( #[allow(unsafe_code)] unsafe { &*(std::ptr::from_ref::<[u8]>(digest) as *const oid) @@ -146,6 +158,8 @@ impl oid { pub fn is_null(&self) -> bool { match self.kind() { Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(), + #[cfg(feature = "sha256")] + Kind::Sha256 => &self.bytes == oid::null_sha256().as_bytes(), } } @@ -154,6 +168,8 @@ impl oid { pub fn is_empty_blob(&self) -> bool { match self.kind() { Kind::Sha1 => &self.bytes == oid::empty_blob_sha1().as_bytes(), + #[cfg(feature = "sha256")] + Kind::Sha256 => &self.bytes == oid::empty_blob_sha256().as_bytes(), } } @@ -162,6 +178,8 @@ impl oid { pub fn is_empty_tree(&self) -> bool { match self.kind() { Kind::Sha1 => &self.bytes == oid::empty_tree_sha1().as_bytes(), + #[cfg(feature = "sha256")] + Kind::Sha256 => &self.bytes == oid::empty_tree_sha256().as_bytes(), } } } @@ -192,16 +210,37 @@ impl oid { oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref()) } - /// Returns an oid representing the hash of an empty blob. + /// Returns a Sha256 digest with all bytes being initialized to zero. + #[inline] + #[cfg(feature = "sha256")] + pub(crate) fn null_sha256() -> &'static Self { + oid::from_bytes([0u8; SIZE_OF_SHA256_DIGEST].as_ref()) + } + + /// Returns an oid representing the SHA-1 hash of an empty blob. #[inline] pub(crate) fn empty_blob_sha1() -> &'static Self { - oid::from_bytes(b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91") + oid::from_bytes(EMPTY_BLOB_SHA1) + } + + /// Returns an oid representing the SHA-256 hash of an empty blob. + #[inline] + #[cfg(feature = "sha256")] + pub(crate) fn empty_blob_sha256() -> &'static Self { + oid::from_bytes(EMPTY_BLOB_SHA256) } - /// Returns an oid representing the hash of an empty tree. + /// Returns an oid representing the SHA-1 hash of an empty tree. #[inline] pub(crate) fn empty_tree_sha1() -> &'static Self { - oid::from_bytes(b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04") + oid::from_bytes(EMPTY_TREE_SHA1) + } + + /// Returns an oid representing the SHA-256 hash of an empty tree. + #[inline] + #[cfg(feature = "sha256")] + pub(crate) fn empty_tree_sha256() -> &'static Self { + oid::from_bytes(EMPTY_TREE_SHA256) } } @@ -225,6 +264,8 @@ impl ToOwned for oid { fn to_owned(&self) -> Self::Owned { match self.kind() { Kind::Sha1 => ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")), + #[cfg(feature = "sha256")] + Kind::Sha256 => ObjectId::Sha256(self.bytes.try_into().expect("no bug in hash detection")), } } } diff --git a/gix-hash/tests/hash/hasher.rs b/gix-hash/tests/hash/hasher.rs index ee73631d69c..196a3a1e407 100644 --- a/gix-hash/tests/hash/hasher.rs +++ b/gix-hash/tests/hash/hasher.rs @@ -14,10 +14,21 @@ fn size_of_hasher() { } #[test] -fn size_of_try_finalize_return_type() { +#[cfg(all(not(feature = "sha256"), feature = "sha1"))] +fn size_of_try_finalize_return_type_sha1_only() { assert_eq!( std::mem::size_of::>(), 21, "The size of the return value is just 1 byte larger than just returning the object hash itself" ); } + +#[test] +#[cfg(all(feature = "sha256", feature = "sha1"))] +fn size_of_try_finalize_return_type_sha1_and_sha256() { + assert_eq!( + std::mem::size_of::>(), + 34, + "The size of the return value is just 1 byte larger than just returning the object hash itself" + ); +} diff --git a/gix-hash/tests/hash/oid.rs b/gix-hash/tests/hash/oid.rs index e1e23e7840a..a0a344d1639 100644 --- a/gix-hash/tests/hash/oid.rs +++ b/gix-hash/tests/hash/oid.rs @@ -20,6 +20,13 @@ fn is_null() { assert!(gix_hash::Kind::Sha1.null().as_ref().is_null()); } +#[test] +#[cfg(feature = "sha256")] +fn is_null_sha256() { + assert!(gix_hash::Kind::Sha256.null().is_null()); + assert!(gix_hash::Kind::Sha256.null().as_ref().is_null()); +} + #[test] fn is_empty_blob() { let empty_blob = gix_hash::ObjectId::empty_blob(gix_hash::Kind::Sha1); diff --git a/gix-hash/tests/hash/prefix.rs b/gix-hash/tests/hash/prefix.rs index 4959fd7149f..06d2907756e 100644 --- a/gix-hash/tests/hash/prefix.rs +++ b/gix-hash/tests/hash/prefix.rs @@ -4,7 +4,7 @@ mod cmp_oid { use crate::hex_to_id; #[test] - fn it_detects_inequality() { + fn it_detects_inequality_sha1() { let prefix = gix_hash::Prefix::new(&hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb3"), 7).unwrap(); assert_eq!( prefix.cmp_oid(&hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3")), @@ -18,7 +18,30 @@ mod cmp_oid { } #[test] - fn it_detects_equality() { + #[cfg(feature = "sha256")] + fn it_detects_inequality_sha256() { + let prefix = gix_hash::Prefix::new( + &hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb338b6efb338b6efb338b6efb3"), + 7, + ) + .unwrap(); + assert_eq!( + prefix.cmp_oid(&hex_to_id( + "a920bbb055e1efb9080592a409d3975738b6efb338b6efb338b6efb338b6efb3" + )), + Ordering::Greater + ); + assert_eq!( + prefix.cmp_oid(&hex_to_id( + "b920bbf055e1efb9080592a409d3975738b6efb338b6efb338b6efb338b6efb3" + )), + Ordering::Less + ); + assert_eq!(prefix.to_string(), "b920bbb"); + } + + #[test] + fn it_detects_equality_sha1() { let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3"); let prefix = gix_hash::Prefix::new(&id, 6).unwrap(); assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); @@ -28,6 +51,19 @@ mod cmp_oid { ); assert_eq!(prefix.to_string(), "a920bb"); } + + #[test] + #[cfg(feature = "sha256")] + fn it_detects_equality_sha256() { + let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb338b6efb338b6efb338b6efb3"); + let prefix = gix_hash::Prefix::new(&id, 6).unwrap(); + assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); + assert_eq!( + prefix.cmp_oid(&hex_to_id("a920bbffffffffffffffffffffffffffffffffff")), + Ordering::Equal + ); + assert_eq!(prefix.to_string(), "a920bb"); + } } mod new { @@ -38,7 +74,7 @@ mod new { use crate::hex_to_id; #[test] - fn various_valid_inputs() { + fn various_valid_inputs_sha1() { let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; let oid = hex_to_id(oid_hex); @@ -53,6 +89,23 @@ mod new { } } + #[test] + #[cfg(feature = "sha256")] + fn various_valid_inputs_sha256() { + let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcdedabcdedabcdedabcdedabcd"; + let oid = hex_to_id(oid_hex); + + for hex_len in 4..oid.kind().len_in_hex() { + let mut expected = String::from(&oid_hex[..hex_len]); + let num_of_zeros = oid.kind().len_in_hex() - hex_len; + expected.extend(std::iter::repeat_n('0', num_of_zeros)); + let prefix = gix_hash::Prefix::new(&oid, hex_len).unwrap(); + assert_eq!(prefix.as_oid().to_hex().to_string(), expected, "{hex_len}"); + assert_eq!(prefix.hex_len(), hex_len); + assert_eq!(prefix.cmp_oid(&oid), Ordering::Equal); + } + } + #[test] fn errors_if_hex_len_is_longer_than_oid_len_in_hex() { let kind = Kind::Sha1; @@ -107,13 +160,23 @@ mod try_from { } #[test] - fn id_to_long() { + #[cfg(all(not(feature = "sha256"), feature = "sha1"))] + fn id_too_long() { let input = "abcdefabcdefabcdefabcdefabcdefabcdefabcd123123123123123123"; let expected = Error::TooLong { hex_len: 58 }; let actual = Prefix::try_from(input).unwrap_err(); assert_eq!(actual, expected); } + #[test] + #[cfg(all(feature = "sha256", feature = "sha1"))] + fn id_too_long() { + let input = "abcdefabcdefabcdefabcdefabcdefabcdefabcd123123123123123123123123123123"; + let expected = Error::TooLong { hex_len: 70 }; + let actual = Prefix::try_from(input).unwrap_err(); + assert_eq!(actual, expected); + } + #[test] fn invalid_chars() { let input = "abcdfOsd";