1use core::fmt;
2use sha1::{Digest, Sha1};
3use sha2::Sha256;
4
5use crate::{Error, Result, io::SliceWriter};
6
7#[cfg(feature = "read")]
8mod parse;
9#[cfg(feature = "read")]
10pub use parse::{Commit, Tag, Tree, TreeEntries, TreeEntry};
11
12#[cfg(feature = "read")]
13mod links;
14#[cfg(feature = "read")]
15pub use links::{LinkDecoder, LinkLimits, LinkProgress, LinkRelation, LinkStatus};
16
17#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)]
19pub enum ObjectFormat {
20 #[default]
21 Sha1,
22 Sha256,
23}
24
25impl ObjectFormat {
26 pub const fn name(self) -> &'static str {
27 match self {
28 Self::Sha1 => "sha1",
29 Self::Sha256 => "sha256",
30 }
31 }
32 pub const fn id_length(self) -> usize {
33 match self {
34 Self::Sha1 => 20,
35 Self::Sha256 => 32,
36 }
37 }
38 pub const fn hex_length(self) -> usize {
39 self.id_length() * 2
40 }
41 pub const fn zero_id(self) -> ObjectId {
42 match self {
43 Self::Sha1 => ObjectId::Sha1([0; 20]),
44 Self::Sha256 => ObjectId::Sha256([0; 32]),
45 }
46 }
47 pub const fn capability(self) -> &'static [u8] {
48 match self {
49 Self::Sha1 => b"object-format=sha1",
50 Self::Sha256 => b"object-format=sha256",
51 }
52 }
53 pub(crate) fn hash(self) -> Hash {
54 match self {
55 Self::Sha1 => Hash::Sha1(Sha1::new()),
56 Self::Sha256 => Hash::Sha256(Sha256::new()),
57 }
58 }
59 pub(crate) fn checked_hash(self) -> CheckedHash {
60 match self {
61 Self::Sha1 => CheckedHash::Sha1(sha1_checked::Sha1::default()),
62 Self::Sha256 => CheckedHash::Sha256(Sha256::new()),
63 }
64 }
65 #[cfg(feature = "read")]
66 pub(crate) fn digest(self, bytes: &[u8]) -> Result<ObjectId> {
67 let mut hash = self.checked_hash();
68 hash.update(bytes);
69 hash.finish()
70 }
71}
72
73#[allow(clippy::large_enum_variant)]
76#[derive(Clone)]
77pub(crate) enum CheckedHash {
78 Sha1(sha1_checked::Sha1),
79 Sha256(Sha256),
80}
81impl CheckedHash {
82 pub(crate) fn update(&mut self, bytes: &[u8]) {
83 match self {
84 Self::Sha1(hash) => hash.update(bytes),
85 Self::Sha256(hash) => hash.update(bytes),
86 }
87 }
88 pub(crate) fn finish(self) -> Result<ObjectId> {
89 match self {
90 Self::Sha1(hash) => match hash.try_finalize() {
91 sha1_checked::CollisionResult::Ok(digest) => Ok(ObjectId::Sha1(digest.into())),
92 _ => Err(Error::InvalidObject),
93 },
94 Self::Sha256(hash) => Ok(ObjectId::Sha256(hash.finalize().into())),
95 }
96 }
97}
98
99#[derive(Clone)]
100pub(crate) enum Hash {
101 Sha1(Sha1),
102 Sha256(Sha256),
103}
104impl Hash {
105 pub(crate) fn update(&mut self, bytes: &[u8]) {
106 match self {
107 Self::Sha1(hash) => hash.update(bytes),
108 Self::Sha256(hash) => hash.update(bytes),
109 }
110 }
111 pub(crate) fn finish(self) -> ObjectId {
112 match self {
113 Self::Sha1(hash) => ObjectId::Sha1(hash.finalize().into()),
114 Self::Sha256(hash) => ObjectId::Sha256(hash.finalize().into()),
115 }
116 }
117}
118
119#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
121pub enum ObjectId {
122 Sha1([u8; 20]),
123 Sha256([u8; 32]),
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub struct HexId {
129 bytes: [u8; 64],
130 length: usize,
131}
132impl HexId {
133 pub fn as_bytes(&self) -> &[u8] {
134 &self.bytes[..self.length]
135 }
136 pub fn as_slice(&self) -> &[u8] {
137 self.as_bytes()
138 }
139}
140impl core::ops::Deref for HexId {
141 type Target = [u8];
142 fn deref(&self) -> &[u8] {
143 self.as_bytes()
144 }
145}
146
147impl ObjectId {
148 pub fn from_hex(hex: &[u8]) -> Result<Self> {
149 let format = match hex.len() {
150 40 => ObjectFormat::Sha1,
151 64 => ObjectFormat::Sha256,
152 _ => return Err(Error::InvalidObjectId),
153 };
154 let mut bytes = [0; 32];
155 for (destination, pair) in bytes[..format.id_length()]
156 .iter_mut()
157 .zip(hex.chunks_exact(2))
158 {
159 *destination = (nibble(pair[0])? << 4) | nibble(pair[1])?;
160 }
161 Self::from_bytes(format, &bytes[..format.id_length()])
162 }
163
164 pub fn from_bytes(format: ObjectFormat, bytes: &[u8]) -> Result<Self> {
165 Ok(match format {
166 ObjectFormat::Sha1 => Self::Sha1(bytes.try_into().map_err(|_| Error::InvalidObjectId)?),
167 ObjectFormat::Sha256 => {
168 Self::Sha256(bytes.try_into().map_err(|_| Error::InvalidObjectId)?)
169 }
170 })
171 }
172 pub const fn format(self) -> ObjectFormat {
173 match self {
174 Self::Sha1(_) => ObjectFormat::Sha1,
175 Self::Sha256(_) => ObjectFormat::Sha256,
176 }
177 }
178 pub fn as_bytes(&self) -> &[u8] {
179 match self {
180 Self::Sha1(bytes) => bytes,
181 Self::Sha256(bytes) => bytes,
182 }
183 }
184 pub fn is_zero(self) -> bool {
185 self.as_bytes().iter().all(|&byte| byte == 0)
186 }
187 pub fn to_hex(self) -> HexId {
188 const HEX: &[u8; 16] = b"0123456789abcdef";
189 let mut result = HexId {
190 bytes: [0; 64],
191 length: self.format().hex_length(),
192 };
193 for (i, byte) in self.as_bytes().iter().enumerate() {
194 result.bytes[i * 2] = HEX[(byte >> 4) as usize];
195 result.bytes[i * 2 + 1] = HEX[(byte & 15) as usize];
196 }
197 result
198 }
199}
200
201impl fmt::Display for ObjectId {
202 fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
203 let hex = self.to_hex();
204 let text = core::str::from_utf8(&hex).map_err(|_| fmt::Error)?;
205 output.write_str(text)
206 }
207}
208
209fn nibble(byte: u8) -> Result<u8> {
210 match byte {
211 b'0'..=b'9' => Ok(byte - b'0'),
212 b'a'..=b'f' => Ok(byte - b'a' + 10),
213 b'A'..=b'F' => Ok(byte - b'A' + 10),
214 _ => Err(Error::InvalidObjectId),
215 }
216}
217
218#[derive(Clone, Copy, Debug, Eq, PartialEq)]
219#[repr(u8)]
220pub enum ObjectKind {
221 Commit = 1,
222 Tree = 2,
223 Blob = 3,
224 Tag = 4,
225}
226
227impl ObjectKind {
228 pub fn name(self) -> &'static str {
229 match self {
230 Self::Commit => "commit",
231 Self::Tree => "tree",
232 Self::Blob => "blob",
233 Self::Tag => "tag",
234 }
235 }
236
237 #[cfg(feature = "read")]
238 pub(crate) fn from_name(name: &[u8]) -> Result<Self> {
239 match name {
240 b"commit" => Ok(Self::Commit),
241 b"tree" => Ok(Self::Tree),
242 b"blob" => Ok(Self::Blob),
243 b"tag" => Ok(Self::Tag),
244 _ => Err(Error::InvalidObject),
245 }
246 }
247
248 #[cfg(feature = "read")]
249 pub(crate) fn from_tag_header(line: &[u8]) -> Result<Self> {
250 Self::from_name(line.strip_prefix(b"type ").ok_or(Error::InvalidObject)?)
251 }
252}
253
254#[derive(Clone, Copy, Debug)]
255pub struct Object<'a> {
256 pub kind: ObjectKind,
257 pub data: &'a [u8],
258}
259
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
263pub struct ObjectInfo {
264 pub kind: ObjectKind,
265 pub size: u64,
266}
267
268impl ObjectInfo {
269 #[cfg(feature = "read")]
272 pub fn from_header(header: &[u8]) -> Result<Self> {
273 let header = header.strip_suffix(&[0]).ok_or(Error::InvalidObject)?;
274 let space = header
275 .iter()
276 .position(|&byte| byte == b' ')
277 .ok_or(Error::InvalidObject)?;
278 let kind = ObjectKind::from_name(&header[..space])?;
279 let decimal = &header[space + 1..];
280 if decimal.is_empty()
281 || decimal.len() > 20
282 || !decimal[0].is_ascii_digit()
283 || (decimal.len() > 1 && decimal[0] == b'0')
284 {
285 return Err(Error::InvalidObject);
286 }
287 let size = core::str::from_utf8(decimal)
288 .map_err(|_| Error::InvalidObject)?
289 .parse()
290 .map_err(|_| Error::InvalidObject)?;
291 Ok(Self { kind, size })
292 }
293
294 pub fn header<'header>(&self, storage: &'header mut [u8]) -> Result<&'header [u8]> {
296 use core::fmt::Write;
297 let mut header = SliceWriter::new(storage);
298 write!(&mut header, "{} {}\0", self.kind.name(), self.size)
299 .map_err(|_| Error::BufferTooSmall)?;
300 Ok(header.finish())
301 }
302}
303
304#[derive(Clone)]
307pub struct ObjectDigest {
308 hash: CheckedHash,
309 remaining: u64,
310}
311
312impl ObjectDigest {
313 pub fn new(info: ObjectInfo, format: ObjectFormat) -> Self {
314 let mut hash = format.checked_hash();
315 let mut header = [0; 32];
316 hash.update(
317 info.header(&mut header)
318 .expect("object header fits in 32 bytes"),
319 );
320 Self {
321 hash,
322 remaining: info.size,
323 }
324 }
325
326 pub fn remaining(&self) -> u64 {
327 self.remaining
328 }
329
330 pub fn update(&mut self, bytes: &[u8]) -> Result<()> {
331 if bytes.len() as u64 > self.remaining {
332 return Err(Error::InvalidObject);
333 }
334 self.hash.update(bytes);
335 self.remaining -= bytes.len() as u64;
336 Ok(())
337 }
338
339 pub fn finish(&self) -> Result<ObjectId> {
340 if self.remaining != 0 {
341 return Err(Error::InvalidObject);
342 }
343 self.hash.clone().finish()
344 }
345}
346
347impl<'a> Object<'a> {
348 #[cfg(feature = "read")]
352 pub fn from_loose(bytes: &'a [u8], expected: ObjectId) -> Result<Self> {
353 let end = bytes
354 .iter()
355 .take(32)
356 .position(|&byte| byte == 0)
357 .ok_or(Error::InvalidObject)?
358 + 1;
359 let info = ObjectInfo::from_header(&bytes[..end])?;
360 let data = &bytes[end..];
361 if data.len() as u64 != info.size || expected.format().digest(bytes)? != expected {
362 return Err(Error::InvalidObject);
363 }
364 Ok(Self {
365 kind: info.kind,
366 data,
367 })
368 }
369
370 pub fn info(&self) -> ObjectInfo {
371 ObjectInfo {
372 kind: self.kind,
373 size: self.data.len() as u64,
374 }
375 }
376
377 pub fn header<'header>(&self, storage: &'header mut [u8]) -> Result<&'header [u8]> {
378 self.info().header(storage)
379 }
380
381 pub fn id(&self) -> ObjectId {
382 self.id_with_format(ObjectFormat::Sha1)
383 }
384
385 pub fn id_with_format(&self, format: ObjectFormat) -> ObjectId {
386 let mut hash = format.hash();
389 hash.update(self.header(&mut [0; 32]).expect("object header fits"));
390 hash.update(self.data);
391 hash.finish()
392 }
393}
394
395#[cfg(test)]
396mod collision_tests;