1use crate::types::{
2 DeltaError, PlacedCommand,
3 DELTA_ADD_HEADER, DELTA_BIGADD_HEADER, DELTA_BIGCOPY_PAYLOAD,
4 DELTA_CMD_ADD, DELTA_CMD_BIGADD, DELTA_CMD_BIGCOPY, DELTA_CMD_BIGMOVE,
5 DELTA_CMD_COPY, DELTA_CMD_END, DELTA_CMD_MOVE,
6 DELTA_COPY_PAYLOAD, DELTA_CRC_SIZE, DELTA_FLAG_INPLACE,
7 DELTA_HEADER_SIZE, DELTA_HEADER_SIZE_LARGE,
8 DELTA_MAGIC, DELTA_MAGIC_LARGE,
9 DELTA_U32_SIZE, DELTA_U64_SIZE,
10};
11
12const U32_MAX: usize = u32::MAX as usize;
14
15fn check_u32(val: usize, field: &str) -> Result<u32, DeltaError> {
16 u32::try_from(val)
17 .map_err(|_| DeltaError::InvalidFormat(format!("{field} exceeds 4 GiB (32-bit format limit)")))
18}
19
20pub fn encode_delta(
28 commands: &[PlacedCommand],
29 inplace: bool,
30 version_size: usize,
31 src_crc: &[u8; DELTA_CRC_SIZE],
32 dst_crc: &[u8; DELTA_CRC_SIZE],
33) -> Result<Vec<u8>, DeltaError> {
34 let mut out = Vec::new();
35 out.extend_from_slice(DELTA_MAGIC);
36 out.push(if inplace { DELTA_FLAG_INPLACE } else { 0 });
37 out.extend_from_slice(&check_u32(version_size, "version_size")?.to_be_bytes());
38 out.extend_from_slice(src_crc);
39 out.extend_from_slice(dst_crc);
40
41 for cmd in commands {
42 match cmd {
43 PlacedCommand::Copy { src, dst, length } => {
44 out.push(DELTA_CMD_COPY);
45 out.extend_from_slice(&check_u32(*src, "copy src offset")?.to_be_bytes());
46 out.extend_from_slice(&check_u32(*dst, "copy dst offset")?.to_be_bytes());
47 out.extend_from_slice(&check_u32(*length, "copy length")?.to_be_bytes());
48 }
49 PlacedCommand::Add { dst, data } => {
50 out.push(DELTA_CMD_ADD);
51 out.extend_from_slice(&check_u32(*dst, "add dst offset")?.to_be_bytes());
52 out.extend_from_slice(&check_u32(data.len(), "add length")?.to_be_bytes());
53 out.extend_from_slice(data);
54 }
55 PlacedCommand::Move { .. } => {
56 return Err(DeltaError::InvalidFormat(
57 "MOVE commands require DLT\\x04 format; use encode_delta_large".into(),
58 ));
59 }
60 }
61 }
62
63 out.push(DELTA_CMD_END);
64 Ok(out)
65}
66
67pub fn encode_delta_large(
79 commands: &[PlacedCommand],
80 inplace: bool,
81 version_size: usize,
82 src_crc: &[u8; DELTA_CRC_SIZE],
83 dst_crc: &[u8; DELTA_CRC_SIZE],
84 force_large: bool,
85) -> Vec<u8> {
86 let mut out = Vec::new();
87 out.extend_from_slice(DELTA_MAGIC_LARGE);
88 out.push(if inplace { DELTA_FLAG_INPLACE } else { 0 });
89 out.extend_from_slice(&(version_size as u64).to_be_bytes());
90 out.extend_from_slice(src_crc);
91 out.extend_from_slice(dst_crc);
92
93 for cmd in commands {
94 match cmd {
95 PlacedCommand::Copy { src, dst, length } => {
96 encode_3field(&mut out, DELTA_CMD_COPY, DELTA_CMD_BIGCOPY, *src, *dst, *length, force_large);
97 }
98 PlacedCommand::Add { dst, data } => {
99 if !force_large && *dst <= U32_MAX && data.len() <= U32_MAX {
100 out.push(DELTA_CMD_ADD);
101 out.extend_from_slice(&(*dst as u32).to_be_bytes());
102 out.extend_from_slice(&(data.len() as u32).to_be_bytes());
103 } else {
104 out.push(DELTA_CMD_BIGADD);
105 out.extend_from_slice(&(*dst as u64).to_be_bytes());
106 out.extend_from_slice(&(data.len() as u64).to_be_bytes());
107 }
108 out.extend_from_slice(data);
109 }
110 PlacedCommand::Move { src, dst, length } => {
111 encode_3field(&mut out, DELTA_CMD_MOVE, DELTA_CMD_BIGMOVE, *src, *dst, *length, force_large);
112 }
113 }
114 }
115
116 out.push(DELTA_CMD_END);
117 out
118}
119
120#[inline]
125fn encode_3field(out: &mut Vec<u8>, small: u8, big: u8, a: usize, b: usize, c: usize, force_large: bool) {
126 if !force_large && a <= U32_MAX && b <= U32_MAX && c <= U32_MAX {
127 out.push(small);
128 out.extend_from_slice(&(a as u32).to_be_bytes());
129 out.extend_from_slice(&(b as u32).to_be_bytes());
130 out.extend_from_slice(&(c as u32).to_be_bytes());
131 } else {
132 out.push(big);
133 out.extend_from_slice(&(a as u64).to_be_bytes());
134 out.extend_from_slice(&(b as u64).to_be_bytes());
135 out.extend_from_slice(&(c as u64).to_be_bytes());
136 }
137}
138
139pub fn decode_delta(
146 data: &[u8],
147) -> Result<(Vec<PlacedCommand>, bool, usize, [u8; DELTA_CRC_SIZE], [u8; DELTA_CRC_SIZE]), DeltaError> {
148 if data.len() < 4 {
149 return Err(DeltaError::InvalidFormat("not a delta file".into()));
150 }
151 match &data[..4] {
152 m if m == DELTA_MAGIC => decode_small(data),
153 m if m == DELTA_MAGIC_LARGE => decode_large(data),
154 _ => Err(DeltaError::InvalidFormat("not a delta file".into())),
155 }
156}
157
158fn decode_small(
159 data: &[u8],
160) -> Result<(Vec<PlacedCommand>, bool, usize, [u8; DELTA_CRC_SIZE], [u8; DELTA_CRC_SIZE]), DeltaError> {
161 if data.len() < DELTA_HEADER_SIZE {
162 return Err(DeltaError::InvalidFormat("not a delta file".into()));
163 }
164 let inplace = data[4] & DELTA_FLAG_INPLACE != 0;
165 let version_size = u32::from_be_bytes([data[5], data[6], data[7], data[8]]) as usize;
166 let crc_offset = 9; let mut src_crc = [0u8; DELTA_CRC_SIZE];
168 let mut dst_crc = [0u8; DELTA_CRC_SIZE];
169 src_crc.copy_from_slice(&data[crc_offset..crc_offset + DELTA_CRC_SIZE]);
170 dst_crc.copy_from_slice(&data[crc_offset + DELTA_CRC_SIZE..crc_offset + 2 * DELTA_CRC_SIZE]);
171
172 let mut pos = DELTA_HEADER_SIZE;
173 let mut commands = Vec::new();
174 let mut saw_end = false;
175
176 while pos < data.len() {
177 let t = data[pos];
178 pos += 1;
179 match t {
180 DELTA_CMD_END => { saw_end = true; break; }
181
182 DELTA_CMD_COPY => {
183 if pos + DELTA_COPY_PAYLOAD > data.len() { return Err(DeltaError::UnexpectedEof); }
184 let src = read_u32(data, pos); pos += DELTA_U32_SIZE;
185 let dst = read_u32(data, pos); pos += DELTA_U32_SIZE;
186 let length = read_u32(data, pos); pos += DELTA_U32_SIZE;
187 validate_placed_range(dst, length, version_size, "copy")?;
188 commands.push(PlacedCommand::Copy { src, dst, length });
189 }
190
191 DELTA_CMD_ADD => {
192 if pos + DELTA_ADD_HEADER > data.len() { return Err(DeltaError::UnexpectedEof); }
193 let dst = read_u32(data, pos); pos += DELTA_U32_SIZE;
194 let length = read_u32(data, pos); pos += DELTA_U32_SIZE;
195 if length > data.len() - pos { return Err(DeltaError::UnexpectedEof); }
196 validate_placed_range(dst, length, version_size, "add")?;
197 commands.push(PlacedCommand::Add { dst, data: data[pos..pos + length].to_vec() });
198 pos += length;
199 }
200
201 DELTA_CMD_BIGCOPY | DELTA_CMD_BIGADD | DELTA_CMD_MOVE | DELTA_CMD_BIGMOVE => {
203 return Err(DeltaError::InvalidFormat(format!(
204 "command type {} requires DLT\\x04 format", t
205 )));
206 }
207
208 _ => {
209 return Err(DeltaError::InvalidFormat(format!(
210 "unknown command type: {}", t
211 )));
212 }
213 }
214 }
215
216 finish_decode(commands, saw_end, pos, data.len(), inplace, version_size, src_crc, dst_crc)
217}
218
219fn decode_large(
220 data: &[u8],
221) -> Result<(Vec<PlacedCommand>, bool, usize, [u8; DELTA_CRC_SIZE], [u8; DELTA_CRC_SIZE]), DeltaError> {
222 if data.len() < DELTA_HEADER_SIZE_LARGE {
223 return Err(DeltaError::InvalidFormat("not a delta file".into()));
224 }
225 let inplace = data[4] & DELTA_FLAG_INPLACE != 0;
226 let version_size = u64::from_be_bytes([
227 data[5], data[6], data[7], data[8],
228 data[9], data[10], data[11], data[12],
229 ]) as usize;
230 let crc_offset = 13; let mut src_crc = [0u8; DELTA_CRC_SIZE];
232 let mut dst_crc = [0u8; DELTA_CRC_SIZE];
233 src_crc.copy_from_slice(&data[crc_offset..crc_offset + DELTA_CRC_SIZE]);
234 dst_crc.copy_from_slice(&data[crc_offset + DELTA_CRC_SIZE..crc_offset + 2 * DELTA_CRC_SIZE]);
235
236 let mut pos = DELTA_HEADER_SIZE_LARGE;
237 let mut commands = Vec::new();
238 let mut saw_end = false;
239
240 while pos < data.len() {
241 let t = data[pos];
242 pos += 1;
243 match t {
244 DELTA_CMD_END => { saw_end = true; break; }
245
246 DELTA_CMD_COPY => {
247 if pos + DELTA_COPY_PAYLOAD > data.len() { return Err(DeltaError::UnexpectedEof); }
248 let src = read_u32(data, pos); pos += DELTA_U32_SIZE;
249 let dst = read_u32(data, pos); pos += DELTA_U32_SIZE;
250 let length = read_u32(data, pos); pos += DELTA_U32_SIZE;
251 validate_placed_range(dst, length, version_size, "copy")?;
252 commands.push(PlacedCommand::Copy { src, dst, length });
253 }
254
255 DELTA_CMD_ADD => {
256 if pos + DELTA_ADD_HEADER > data.len() { return Err(DeltaError::UnexpectedEof); }
257 let dst = read_u32(data, pos); pos += DELTA_U32_SIZE;
258 let length = read_u32(data, pos); pos += DELTA_U32_SIZE;
259 if length > data.len() - pos { return Err(DeltaError::UnexpectedEof); }
260 validate_placed_range(dst, length, version_size, "add")?;
261 commands.push(PlacedCommand::Add { dst, data: data[pos..pos + length].to_vec() });
262 pos += length;
263 }
264
265 DELTA_CMD_BIGCOPY => {
266 if pos + DELTA_BIGCOPY_PAYLOAD > data.len() { return Err(DeltaError::UnexpectedEof); }
267 let src = read_u64(data, pos); pos += DELTA_U64_SIZE;
268 let dst = read_u64(data, pos); pos += DELTA_U64_SIZE;
269 let length = read_u64(data, pos); pos += DELTA_U64_SIZE;
270 validate_placed_range(dst, length, version_size, "bigcopy")?;
271 commands.push(PlacedCommand::Copy { src, dst, length });
272 }
273
274 DELTA_CMD_BIGADD => {
275 if pos + DELTA_BIGADD_HEADER > data.len() { return Err(DeltaError::UnexpectedEof); }
276 let dst = read_u64(data, pos); pos += DELTA_U64_SIZE;
277 let length = read_u64(data, pos); pos += DELTA_U64_SIZE;
278 if length > data.len() - pos { return Err(DeltaError::UnexpectedEof); }
279 validate_placed_range(dst, length, version_size, "bigadd")?;
280 commands.push(PlacedCommand::Add { dst, data: data[pos..pos + length].to_vec() });
281 pos += length;
282 }
283
284 DELTA_CMD_MOVE => {
285 if pos + DELTA_COPY_PAYLOAD > data.len() { return Err(DeltaError::UnexpectedEof); }
286 let src = read_u32(data, pos); pos += DELTA_U32_SIZE;
287 let dst = read_u32(data, pos); pos += DELTA_U32_SIZE;
288 let length = read_u32(data, pos); pos += DELTA_U32_SIZE;
289 validate_placed_range(dst, length, version_size, "move")?;
290 commands.push(PlacedCommand::Move { src, dst, length });
291 }
292
293 DELTA_CMD_BIGMOVE => {
294 if pos + DELTA_BIGCOPY_PAYLOAD > data.len() { return Err(DeltaError::UnexpectedEof); }
295 let src = read_u64(data, pos); pos += DELTA_U64_SIZE;
296 let dst = read_u64(data, pos); pos += DELTA_U64_SIZE;
297 let length = read_u64(data, pos); pos += DELTA_U64_SIZE;
298 validate_placed_range(dst, length, version_size, "bigmove")?;
299 commands.push(PlacedCommand::Move { src, dst, length });
300 }
301
302 _ => {
303 return Err(DeltaError::InvalidFormat(format!(
304 "unknown command type: {}", t
305 )));
306 }
307 }
308 }
309
310 finish_decode(commands, saw_end, pos, data.len(), inplace, version_size, src_crc, dst_crc)
311}
312
313#[inline]
314fn read_u32(data: &[u8], pos: usize) -> usize {
315 u32::from_be_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]) as usize
316}
317
318#[inline]
319fn read_u64(data: &[u8], pos: usize) -> usize {
320 u64::from_be_bytes([
321 data[pos], data[pos+1], data[pos+2], data[pos+3],
322 data[pos+4], data[pos+5], data[pos+6], data[pos+7],
323 ]) as usize
324}
325
326fn finish_decode(
327 commands: Vec<PlacedCommand>,
328 saw_end: bool,
329 pos: usize,
330 data_len: usize,
331 inplace: bool,
332 version_size: usize,
333 src_crc: [u8; DELTA_CRC_SIZE],
334 dst_crc: [u8; DELTA_CRC_SIZE],
335) -> Result<(Vec<PlacedCommand>, bool, usize, [u8; DELTA_CRC_SIZE], [u8; DELTA_CRC_SIZE]), DeltaError> {
336 if !saw_end {
337 return Err(DeltaError::InvalidFormat("missing END command".into()));
338 }
339 if pos != data_len {
340 return Err(DeltaError::InvalidFormat("trailing data after END".into()));
341 }
342 Ok((commands, inplace, version_size, src_crc, dst_crc))
343}
344
345pub fn is_inplace_delta(data: &[u8]) -> bool {
349 if data.len() < 5 {
350 return false;
351 }
352 let magic = &data[..4];
353 (magic == DELTA_MAGIC || magic == DELTA_MAGIC_LARGE)
354 && data[4] & DELTA_FLAG_INPLACE != 0
355}
356
357fn validate_placed_range(dst: usize, length: usize, version_size: usize, kind: &str) -> Result<(), DeltaError> {
358 if dst > version_size || length > version_size.saturating_sub(dst) {
359 return Err(DeltaError::InvalidFormat(format!(
360 "{} command exceeds version size",
361 kind
362 )));
363 }
364 Ok(())
365}