Charisee | 635618d | 2023-06-01 20:46:00 +0000 | [diff] [blame^] | 1 | use std::{convert::TryInto, fmt}; |
| 2 | |
| 3 | use crate::{ObjectId, SIZE_OF_SHA1_DIGEST}; |
| 4 | |
| 5 | /// A borrowed reference to a hash identifying objects. |
| 6 | /// |
| 7 | /// # Future Proofing |
| 8 | /// |
| 9 | /// In case we wish to support multiple hashes with the same length we cannot discriminate |
| 10 | /// using the slice length anymore. To make that work, we will use the high bits of the |
| 11 | /// internal `bytes` slice length (a fat pointer, pointing to data and its length in bytes) |
| 12 | /// to encode additional information. Before accessing or returning the bytes, a new adjusted |
| 13 | /// slice will be constructed, while the high bits will be used to help resolving the |
| 14 | /// hash `[`kind()`][oid::kind()]`. |
| 15 | /// We expect to have quite a few bits available for such 'conflict resolution' as most hashes aren't longer |
| 16 | /// than 64 bytes. |
| 17 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd)] |
| 18 | #[repr(transparent)] |
| 19 | #[allow(non_camel_case_types)] |
| 20 | #[cfg_attr(feature = "serde1", derive(serde::Serialize))] |
| 21 | pub struct oid { |
| 22 | bytes: [u8], |
| 23 | } |
| 24 | |
| 25 | /// A utility able to format itself with the given amount of characters in hex |
| 26 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd)] |
| 27 | pub struct HexDisplay<'a> { |
| 28 | inner: &'a oid, |
| 29 | hex_len: usize, |
| 30 | } |
| 31 | |
| 32 | impl<'a> fmt::Display for HexDisplay<'a> { |
| 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 34 | let mut hex = crate::Kind::hex_buf(); |
| 35 | let max_len = self.inner.hex_to_buf(hex.as_mut()); |
| 36 | let hex = std::str::from_utf8(&hex[..self.hex_len.min(max_len)]).expect("ascii only in hex"); |
| 37 | f.write_str(hex) |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | impl fmt::Debug for oid { |
| 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { |
| 43 | write!( |
| 44 | f, |
| 45 | "{}({})", |
| 46 | match self.kind() { |
| 47 | crate::Kind::Sha1 => "Sha1", |
| 48 | }, |
| 49 | self.to_hex(), |
| 50 | ) |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | #[derive(Debug, thiserror::Error)] |
| 55 | pub enum Error { |
| 56 | #[error("Cannot instantiate git hash from a digest of length {0}")] |
| 57 | InvalidByteSliceLength(usize), |
| 58 | } |
| 59 | |
| 60 | /// Conversion |
| 61 | impl oid { |
| 62 | /// Try to create a shared object id from a slice of bytes representing a hash `digest` |
| 63 | #[inline] |
| 64 | pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> { |
| 65 | match digest.len() { |
| 66 | 20 => Ok( |
| 67 | #[allow(unsafe_code)] |
| 68 | unsafe { |
| 69 | &*(digest as *const [u8] as *const oid) |
| 70 | }, |
| 71 | ), |
| 72 | len => Err(Error::InvalidByteSliceLength(len)), |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | /// Create an OID from the input `value` slice without performing any safety check. |
| 77 | /// Use only once sure that `value` is a hash of valid length. |
| 78 | pub fn from_bytes_unchecked(value: &[u8]) -> &Self { |
| 79 | Self::from_bytes(value) |
| 80 | } |
| 81 | |
| 82 | /// Only from code that statically assures correct sizes using array conversions |
| 83 | pub(crate) fn from_bytes(value: &[u8]) -> &Self { |
| 84 | #[allow(unsafe_code)] |
| 85 | unsafe { |
| 86 | &*(value as *const [u8] as *const oid) |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | /// Access |
| 92 | impl oid { |
| 93 | /// The kind of hash used for this Digest |
| 94 | #[inline] |
| 95 | pub fn kind(&self) -> crate::Kind { |
| 96 | crate::Kind::from_len_in_bytes(self.bytes.len()) |
| 97 | } |
| 98 | |
| 99 | /// The first byte of the hash, commonly used to partition a set of `Id`s |
| 100 | #[inline] |
| 101 | pub fn first_byte(&self) -> u8 { |
| 102 | self.bytes[0] |
| 103 | } |
| 104 | |
| 105 | /// Interpret this object id as raw byte slice. |
| 106 | #[inline] |
| 107 | pub fn as_bytes(&self) -> &[u8] { |
| 108 | &self.bytes |
| 109 | } |
| 110 | |
| 111 | /// Return a type which can display itself in hexadecimal form with the `len` amount of characters. |
| 112 | #[inline] |
| 113 | pub fn to_hex_with_len(&self, len: usize) -> HexDisplay<'_> { |
| 114 | HexDisplay { |
| 115 | inner: self, |
| 116 | hex_len: len, |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /// Return a type which displays this oid as hex in full. |
| 121 | #[inline] |
| 122 | pub fn to_hex(&self) -> HexDisplay<'_> { |
| 123 | HexDisplay { |
| 124 | inner: self, |
| 125 | hex_len: self.bytes.len() * 2, |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /// Sha1 specific methods |
| 131 | impl oid { |
| 132 | /// Write ourselves to the `out` in hexadecimal notation, returning the amount of written bytes. |
| 133 | /// |
| 134 | /// **Panics** if the buffer isn't big enough to hold twice as many bytes as the current binary size. |
| 135 | #[inline] |
| 136 | #[must_use] |
| 137 | pub fn hex_to_buf(&self, buf: &mut [u8]) -> usize { |
| 138 | let num_hex_bytes = self.bytes.len() * 2; |
| 139 | hex::encode_to_slice(&self.bytes, &mut buf[..num_hex_bytes]).expect("to count correctly"); |
| 140 | num_hex_bytes |
| 141 | } |
| 142 | |
| 143 | /// Write ourselves to `out` in hexadecimal notation |
| 144 | #[inline] |
| 145 | pub fn write_hex_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { |
| 146 | let mut hex = crate::Kind::hex_buf(); |
| 147 | let hex_len = self.hex_to_buf(&mut hex); |
| 148 | out.write_all(&hex[..hex_len]) |
| 149 | } |
| 150 | |
| 151 | /// Returns a Sha1 digest with all bytes being initialized to zero. |
| 152 | #[inline] |
| 153 | pub(crate) fn null_sha1() -> &'static Self { |
| 154 | oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref()) |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | impl AsRef<oid> for &oid { |
| 159 | fn as_ref(&self) -> &oid { |
| 160 | self |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | impl ToOwned for oid { |
| 165 | type Owned = crate::ObjectId; |
| 166 | |
| 167 | fn to_owned(&self) -> Self::Owned { |
| 168 | match self.kind() { |
| 169 | crate::Kind::Sha1 => crate::ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")), |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid { |
| 175 | fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self { |
| 176 | oid::from_bytes(v.as_ref()) |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | impl fmt::Display for &oid { |
| 181 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 182 | for b in self.as_bytes() { |
| 183 | write!(f, "{b:02x}")?; |
| 184 | } |
| 185 | Ok(()) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | impl PartialEq<crate::ObjectId> for &oid { |
| 190 | fn eq(&self, other: &ObjectId) -> bool { |
| 191 | *self == other.as_ref() |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | /// Manually created from a version that uses a slice, and we forcefully try to convert it into a borrowed array of the desired size |
| 196 | /// Could be improved by fitting this into serde |
| 197 | /// Unfortunately the serde::Deserialize derive wouldn't work for borrowed arrays. |
| 198 | #[cfg(feature = "serde1")] |
| 199 | impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a oid { |
| 200 | fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error> |
| 201 | where |
| 202 | D: serde::Deserializer<'de>, |
| 203 | { |
| 204 | struct __Visitor<'de: 'a, 'a> { |
| 205 | marker: std::marker::PhantomData<&'a oid>, |
| 206 | lifetime: std::marker::PhantomData<&'de ()>, |
| 207 | } |
| 208 | impl<'de: 'a, 'a> serde::de::Visitor<'de> for __Visitor<'de, 'a> { |
| 209 | type Value = &'a oid; |
| 210 | fn expecting(&self, __formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 211 | std::fmt::Formatter::write_str(__formatter, "tuple struct Digest") |
| 212 | } |
| 213 | #[inline] |
| 214 | fn visit_newtype_struct<__E>(self, __e: __E) -> std::result::Result<Self::Value, __E::Error> |
| 215 | where |
| 216 | __E: serde::Deserializer<'de>, |
| 217 | { |
| 218 | let __field0: &'a [u8] = match <&'a [u8] as serde::Deserialize>::deserialize(__e) { |
| 219 | Ok(__val) => __val, |
| 220 | Err(__err) => { |
| 221 | return Err(__err); |
| 222 | } |
| 223 | }; |
| 224 | Ok(oid::try_from_bytes(__field0).expect("hash of known length")) |
| 225 | } |
| 226 | #[inline] |
| 227 | fn visit_seq<__A>(self, mut __seq: __A) -> std::result::Result<Self::Value, __A::Error> |
| 228 | where |
| 229 | __A: serde::de::SeqAccess<'de>, |
| 230 | { |
| 231 | let __field0 = match match serde::de::SeqAccess::next_element::<&'a [u8]>(&mut __seq) { |
| 232 | Ok(__val) => __val, |
| 233 | Err(__err) => { |
| 234 | return Err(__err); |
| 235 | } |
| 236 | } { |
| 237 | Some(__value) => __value, |
| 238 | None => { |
| 239 | return Err(serde::de::Error::invalid_length( |
| 240 | 0usize, |
| 241 | &"tuple struct Digest with 1 element", |
| 242 | )); |
| 243 | } |
| 244 | }; |
| 245 | Ok(oid::try_from_bytes(__field0).expect("hash of known length")) |
| 246 | } |
| 247 | } |
| 248 | serde::Deserializer::deserialize_newtype_struct( |
| 249 | deserializer, |
| 250 | "Digest", |
| 251 | __Visitor { |
| 252 | marker: std::marker::PhantomData::<&'a oid>, |
| 253 | lifetime: std::marker::PhantomData, |
| 254 | }, |
| 255 | ) |
| 256 | } |
| 257 | } |