mkit_core/pack/
rewrite.rs1use super::{
9 DecodeLimits, DecodedEntry, DeltaBaseSource, ENTRY_FRAME_LEN, HEADER_LEN, PackError,
10 PackWriter, decode_entries_with, decompress_zstd_entry,
11};
12use crate::hash::Hash;
13use std::collections::HashSet;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub struct Rewritten {
19 pub bytes: Vec<u8>,
21 pub removed: Vec<Hash>,
23 pub rawified: Vec<Hash>,
25 pub unchanged: bool,
27}
28
29#[allow(clippy::implicit_hasher)] pub fn rewrite_excluding<B: DeltaBaseSource>(
46 pack: &[u8],
47 excluded: &HashSet<Hash>,
48 bases: &mut B,
49 limits: DecodeLimits,
50) -> Result<Rewritten, PackError> {
51 let mut rewrite = Rewrite::new(pack, excluded);
52 decode_entries_with(pack, bases, limits, |entry| rewrite.accept(&entry))?;
53 rewrite.finish()
54}
55
56struct Rewrite<'a> {
57 pack: &'a [u8],
58 excluded: &'a HashSet<Hash>,
59 pos: usize,
60 writer: PackWriter,
61 removed: Vec<Hash>,
62 removed_seen: HashSet<Hash>,
63 rawified: Vec<Hash>,
64 writer_error: Option<PackError>,
65}
66
67impl<'a> Rewrite<'a> {
68 fn new(pack: &'a [u8], excluded: &'a HashSet<Hash>) -> Self {
69 Self {
70 pack,
71 excluded,
72 pos: HEADER_LEN,
73 writer: PackWriter::new(),
74 removed: Vec::new(),
75 removed_seen: HashSet::new(),
76 rawified: Vec::new(),
77 writer_error: None,
78 }
79 }
80
81 fn accept(&mut self, entry: &DecodedEntry<'_>) -> Result<(), PackError> {
82 let start = self.pos;
86 let payload_start = start
87 .checked_add(ENTRY_FRAME_LEN)
88 .ok_or(PackError::UnexpectedEof)?;
89 let frame = self
90 .pack
91 .get(start..payload_start)
92 .ok_or(PackError::UnexpectedEof)?;
93 let len = u32::from_le_bytes(
94 frame[1..]
95 .try_into()
96 .map_err(|_| PackError::UnexpectedEof)?,
97 );
98 let payload_end = payload_start
99 .checked_add(usize::try_from(len).map_err(|_| PackError::UnexpectedEof)?)
100 .ok_or(PackError::UnexpectedEof)?;
101 let payload = self
102 .pack
103 .get(payload_start..payload_end)
104 .ok_or(PackError::UnexpectedEof)?;
105 self.pos = payload_end;
106
107 if self.excluded.contains(&entry.id) {
108 if self.removed_seen.insert(entry.id) {
109 self.removed.push(entry.id);
110 }
111 return Ok(());
112 }
113 let base = if entry.from_delta {
114 Some(
115 <Hash>::try_from(payload.get(..32).ok_or(PackError::DeltaEntryTruncated)?)
116 .map_err(|_| PackError::DeltaEntryTruncated)?,
117 )
118 } else {
119 None
120 };
121 let rawify = base.is_some_and(|id| self.excluded.contains(&id));
122 if rawify {
123 self.rawified.push(entry.id);
124 }
125 if self.writer_error.is_some() {
126 return Ok(());
127 }
128 let written = match base {
129 Some(base) if !rawify => {
130 let stream = &payload[32..];
131 if frame[0] == 0x04 {
132 let stream = decompress_zstd_entry(stream)?;
137 self.writer.push_delta(&base, &stream)
138 } else {
139 self.writer.push_delta(&base, stream)
140 }
141 }
142 _ => self.writer.push_raw(entry.id, entry.bytes).map(|_| ()),
143 };
144 if let Err(error) = written {
147 self.writer_error = Some(error);
148 }
149 Ok(())
150 }
151
152 fn finish(self) -> Result<Rewritten, PackError> {
153 let unchanged = self.removed.is_empty() && self.rawified.is_empty();
154 let bytes = if unchanged {
155 drop(self.writer);
157 self.pack.to_vec()
158 } else {
159 if let Some(error) = self.writer_error {
160 return Err(error);
161 }
162 self.writer.finish()?
163 };
164 Ok(Rewritten {
165 bytes,
166 removed: self.removed,
167 rawified: self.rawified,
168 unchanged,
169 })
170 }
171}
172
173#[cfg(test)]
174mod tests;