git_zero_core/cache/
selected.rs1use crate::{
9 Error, ObjectFormat, ObjectId, Repository, Result,
10 io::{SliceWriter, Write},
11 pack,
12};
13
14mod fixed;
15mod stored;
16pub use fixed::{FixedCache, Statistics};
17
18#[derive(Clone, Copy, Debug)]
22pub enum Selection<'a> {
23 All,
24 Indices(&'a [usize]),
25}
26
27impl<'a> Selection<'a> {
28 fn indices(self) -> Option<&'a [usize]> {
29 match self {
30 Self::All => None,
31 Self::Indices(indices) => Some(indices),
32 }
33 }
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct Key([u8; 48]);
40
41impl Key {
42 pub fn derive(
47 repository: &impl Repository,
48 selection: Selection<'_>,
49 ids: &mut [ObjectId],
50 ) -> Result<Self> {
51 let indices = selection.indices();
52 let count = indices.map_or(repository.object_count(), <[usize]>::len);
53 let count32 = u32::try_from(count).map_err(|_| Error::LimitExceeded)?;
54 let ids = ids.get_mut(..count).ok_or(Error::BufferTooSmall)?;
55 if indices.is_some_and(|indices| indices.windows(2).any(|pair| pair[0] >= pair[1])) {
56 return Err(Error::InvalidObject);
57 }
58 let format = repository.object_format();
59 for (position, slot) in ids.iter_mut().enumerate() {
60 let index = indices.map_or(position, |indices| indices[position]);
61 if index >= repository.object_count() {
62 return Err(Error::ObjectNotFound);
63 }
64 repository.object_info(index)?;
65 *slot = repository.object_id(index).ok_or(Error::ObjectNotFound)?;
66 if slot.format() != format {
67 return Err(Error::InvalidObjectId);
68 }
69 }
70 Self::from_ids(format, count32, ids)
71 }
72
73 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
76 let bytes: [u8; 48] = bytes.try_into().map_err(|_| Error::InvalidRequest)?;
77 if &bytes[..5] != b"GZPC\x01"
78 || !matches!(bytes[5], 1 | 2)
79 || bytes[6..12] != [0, 0, 0, 0, 0, 1]
80 {
81 return Err(Error::Unsupported);
82 }
83 Ok(Self(bytes))
84 }
85
86 pub fn as_bytes(&self) -> &[u8; 48] {
87 &self.0
88 }
89 pub fn object_format(self) -> ObjectFormat {
90 if self.0[5] == 1 {
91 ObjectFormat::Sha1
92 } else {
93 ObjectFormat::Sha256
94 }
95 }
96 pub fn object_count(self) -> u32 {
97 u32::from_be_bytes(self.0[12..16].try_into().unwrap())
98 }
99
100 pub fn verify_pack(self, bytes: &[u8], ids: &mut [ObjectId], byte_limit: usize) -> Result<()> {
104 if bytes.len() > byte_limit {
105 return Err(Error::LimitExceeded);
106 }
107 let count = usize::try_from(self.object_count()).map_err(|_| Error::LimitExceeded)?;
108 let ids = ids.get_mut(..count).ok_or(Error::BufferTooSmall)?;
109 stored::read(bytes, self.object_format(), ids)?;
110 if Self::from_ids(self.object_format(), self.object_count(), ids)? != self {
111 return Err(Error::CorruptPack);
112 }
113 Ok(())
114 }
115
116 fn from_ids(format: ObjectFormat, count: u32, ids: &mut [ObjectId]) -> Result<Self> {
117 ids.sort_unstable();
118 if ids.windows(2).any(|pair| pair[0] == pair[1]) {
119 return Err(Error::InvalidObject);
120 }
121 let mut bytes = [0; 48];
122 bytes[..5].copy_from_slice(b"GZPC\x01");
123 bytes[5] = match format {
124 ObjectFormat::Sha1 => 1,
125 ObjectFormat::Sha256 => 2,
126 };
127 bytes[8..12].copy_from_slice(&1u32.to_be_bytes());
128 bytes[12..16].copy_from_slice(&count.to_be_bytes());
129 let mut hash = ObjectFormat::Sha256.hash();
130 hash.update(b"git-zero/selected-pack/v1\0");
131 hash.update(&bytes[..16]);
132 for id in ids {
133 hash.update(id.as_bytes());
134 }
135 bytes[16..].copy_from_slice(hash.finish().as_bytes());
136 Ok(Self(bytes))
137 }
138}
139
140pub trait PackCache {
144 fn get(&mut self, key: Key) -> Result<Option<&[u8]>>;
145 fn put(&mut self, key: Key, pack: &[u8]) -> Result<()>;
146}
147
148impl PackCache for super::NoCache {
149 fn get(&mut self, _: Key) -> Result<Option<&[u8]>> {
150 Ok(None)
151 }
152 fn put(&mut self, _: Key, _: &[u8]) -> Result<()> {
153 Ok(())
154 }
155}
156
157pub struct Buffers<'a> {
160 pub ids: &'a mut [ObjectId],
161 pub pack: &'a mut [u8],
162 pub read: &'a mut [u8],
163 pub byte_limit: usize,
164}
165
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167pub enum Outcome {
168 Hit,
169 Miss,
170}
171
172pub fn write(
178 repository: &impl Repository,
179 selection: Selection<'_>,
180 cache: &mut impl PackCache,
181 buffers: Buffers<'_>,
182 output: &mut impl Write,
183) -> Result<Outcome> {
184 let key = Key::derive(repository, selection, buffers.ids)?;
185 if let Some(bytes) = cache.get(key)? {
186 key.verify_pack(bytes, buffers.ids, buffers.byte_limit)?;
187 output.write_all(bytes)?;
188 return Ok(Outcome::Hit);
189 }
190 let capacity = buffers.pack.len().min(buffers.byte_limit);
191 let mut staging = SliceWriter::new(&mut buffers.pack[..capacity]);
192 match selection {
193 Selection::Indices(indices) => {
194 pack::write_selected_with_scratch(repository, indices, buffers.read, &mut staging)?
195 }
196 Selection::All => pack::write_with_scratch(repository, buffers.read, &mut staging)?,
197 };
198 let bytes = staging.finish();
199 key.verify_pack(bytes, buffers.ids, buffers.byte_limit)?;
200 cache.put(key, bytes)?;
201 output.write_all(bytes)?;
202 Ok(Outcome::Miss)
203}