1use crate::{
4 digest::{Digest, Verifier, VerifyError},
5 tar::{
6 Archive, ArchiveError, ArchiveFinishError, EntryExtractor, ExtractError, FinishError,
7 TransactionalLayerSink,
8 },
9};
10
11pub const OCI_LAYER_TAR: &str = "application/vnd.oci.image.layer.v1.tar";
12pub const OCI_LAYER_GZIP: &str = "application/vnd.oci.image.layer.v1.tar+gzip";
13pub const OCI_LAYER_ZSTD: &str = "application/vnd.oci.image.layer.v1.tar+zstd";
14pub const OCI_NONDISTRIBUTABLE_TAR: &str =
15 "application/vnd.oci.image.layer.nondistributable.v1.tar";
16pub const OCI_NONDISTRIBUTABLE_GZIP: &str =
17 "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip";
18pub const OCI_NONDISTRIBUTABLE_ZSTD: &str =
19 "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd";
20pub const DOCKER_LAYER_GZIP: &str = "application/vnd.docker.image.rootfs.diff.tar.gzip";
21pub const DOCKER_FOREIGN_LAYER_GZIP: &str =
22 "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip";
23pub const DOCKER_LAYER_TAR: &str = "application/vnd.docker.image.rootfs.diff.tar";
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum Encoding {
27 Tar,
28 Gzip,
29 Zstd,
30}
31
32pub fn encoding(media_type: &str) -> Result<Encoding, LayerFormatError> {
33 match media_type {
34 OCI_LAYER_TAR | OCI_NONDISTRIBUTABLE_TAR | DOCKER_LAYER_TAR => Ok(Encoding::Tar),
35 OCI_LAYER_GZIP
36 | OCI_NONDISTRIBUTABLE_GZIP
37 | DOCKER_LAYER_GZIP
38 | DOCKER_FOREIGN_LAYER_GZIP => Ok(Encoding::Gzip),
39 OCI_LAYER_ZSTD | OCI_NONDISTRIBUTABLE_ZSTD => Ok(Encoding::Zstd),
40 _ => Err(LayerFormatError::UnsupportedMediaType),
41 }
42}
43
44#[allow(clippy::large_enum_variant)]
47pub enum Decoder<'a> {
48 Tar,
49 #[cfg(feature = "gzip")]
50 Gzip(gzip_zero::Decoder<'a>),
51 #[cfg(feature = "zstd")]
52 Zstd(zstd_zero::Decoder<'a>),
53 #[doc(hidden)]
54 _Lifetime(core::marker::PhantomData<&'a mut [u8]>),
55}
56
57impl<'a> Decoder<'a> {
58 pub const fn tar() -> Self {
59 Self::Tar
60 }
61
62 #[cfg(feature = "gzip")]
63 pub fn gzip(buffers: gzip_zero::DecoderBuffers<'a>) -> Result<Self, LayerFormatError> {
64 Ok(Self::Gzip(
65 gzip_zero::Decoder::new(buffers).map_err(LayerFormatError::Gzip)?,
66 ))
67 }
68
69 #[cfg(feature = "zstd")]
70 pub fn zstd(buffers: zstd_zero::DecoderBuffers<'a>) -> Result<Self, LayerFormatError> {
71 Ok(Self::Zstd(
72 zstd_zero::Decoder::new(buffers).map_err(LayerFormatError::Zstd)?,
73 ))
74 }
75
76 pub fn decode<'decoder>(
77 &'decoder mut self,
78 input: &'decoder [u8],
79 ) -> Result<DecodeStep<'decoder>, LayerFormatError> {
80 match self {
81 Self::Tar => Ok(if input.is_empty() {
82 DecodeStep::NeedInput { consumed: 0 }
83 } else {
84 DecodeStep::Output {
85 consumed: input.len(),
86 bytes: input,
87 }
88 }),
89 #[cfg(feature = "gzip")]
90 Self::Gzip(decoder) => Ok(
91 match decoder.decode(input).map_err(LayerFormatError::Gzip)? {
92 gzip_zero::DecodeStep::NeedInput { consumed } => {
93 DecodeStep::NeedInput { consumed }
94 }
95 gzip_zero::DecodeStep::MemberStarted { consumed, .. }
96 | gzip_zero::DecodeStep::MemberFinished { consumed } => {
97 DecodeStep::Progress { consumed }
98 }
99 gzip_zero::DecodeStep::Output { consumed, bytes } => {
100 DecodeStep::Output { consumed, bytes }
101 }
102 },
103 ),
104 #[cfg(feature = "zstd")]
105 Self::Zstd(decoder) => Ok(
106 match decoder.decode(input).map_err(LayerFormatError::Zstd)? {
107 zstd_zero::DecodeStep::NeedInput { consumed } => {
108 DecodeStep::NeedInput { consumed }
109 }
110 zstd_zero::DecodeStep::FrameStarted { consumed, .. }
111 | zstd_zero::DecodeStep::FrameFinished { consumed, .. } => {
112 DecodeStep::Progress { consumed }
113 }
114 zstd_zero::DecodeStep::Output { consumed, bytes } => {
115 DecodeStep::Output { consumed, bytes }
116 }
117 },
118 ),
119 Self::_Lifetime(_) => unreachable!(),
120 }
121 }
122
123 pub fn finish(&self) -> Result<(), LayerFormatError> {
124 match self {
125 Self::Tar => Ok(()),
126 #[cfg(feature = "gzip")]
127 Self::Gzip(decoder) => decoder.finish().map_err(LayerFormatError::Gzip),
128 #[cfg(feature = "zstd")]
129 Self::Zstd(decoder) => decoder.finish().map_err(LayerFormatError::Zstd),
130 Self::_Lifetime(_) => unreachable!(),
131 }
132 }
133}
134
135#[derive(Debug, Eq, PartialEq)]
136pub enum DecodeStep<'a> {
137 NeedInput { consumed: usize },
138 Progress { consumed: usize },
139 Output { consumed: usize, bytes: &'a [u8] },
140}
141
142impl DecodeStep<'_> {
143 pub const fn consumed(&self) -> usize {
144 match self {
145 Self::NeedInput { consumed }
146 | Self::Progress { consumed }
147 | Self::Output { consumed, .. } => *consumed,
148 }
149 }
150}
151
152pub struct VerifiedDecoder<'a> {
153 decoder: Decoder<'a>,
154 compressed: Verifier,
155 uncompressed: Option<Verifier>,
156 decompressed_size: u64,
157}
158
159pub struct VerifiedEntryExtractor<'decoder, 'target> {
161 decoder: VerifiedDecoder<'decoder>,
162 extractor: EntryExtractor<'target>,
163 extracted_size: u64,
164}
165
166impl<'decoder, 'target> VerifiedEntryExtractor<'decoder, 'target> {
167 pub const fn new(decoder: VerifiedDecoder<'decoder>, target: &'target [u8]) -> Self {
168 Self {
169 decoder,
170 extractor: EntryExtractor::new(target),
171 extracted_size: 0,
172 }
173 }
174
175 pub fn push<E>(
176 &mut self,
177 input: &[u8],
178 mut output: impl FnMut(&[u8]) -> Result<(), E>,
179 ) -> Result<(), EntryLayerError<E>> {
180 let extractor = &mut self.extractor;
181 let extracted_size = &mut self.extracted_size;
182 self.decoder
183 .push(input, |decoded| {
184 extract_output(extractor, extracted_size, decoded, &mut output)
185 })
186 .map_err(EntryLayerError::Layer)
187 }
188
189 pub fn finish<E>(
190 &mut self,
191 mut output: impl FnMut(&[u8]) -> Result<(), E>,
192 ) -> Result<(), EntryLayerError<E>> {
193 let extractor = &mut self.extractor;
194 let extracted_size = &mut self.extracted_size;
195 self.decoder
196 .finish(|decoded| extract_output(extractor, extracted_size, decoded, &mut output))
197 .map_err(EntryLayerError::Layer)?;
198 self.extractor.finish().map_err(EntryLayerError::Finish)
199 }
200
201 pub const fn compressed_size(&self) -> u64 {
202 self.decoder.compressed_size()
203 }
204
205 pub const fn decompressed_size(&self) -> u64 {
206 self.decoder.decompressed_size()
207 }
208
209 pub const fn extracted_size(&self) -> u64 {
210 self.extracted_size
211 }
212
213 pub const fn found(&self) -> bool {
214 self.extractor.found()
215 }
216}
217
218fn extract_output<E>(
219 extractor: &mut EntryExtractor<'_>,
220 extracted_size: &mut u64,
221 decoded: &[u8],
222 output: &mut impl FnMut(&[u8]) -> Result<(), E>,
223) -> Result<(), ExtractError<E>> {
224 extractor.push(decoded, |contents| {
225 output(contents)?;
226 *extracted_size += contents.len() as u64;
227 Ok(())
228 })
229}
230
231#[derive(Debug, derive_more::Display)]
232pub enum EntryLayerError<E> {
233 #[display("entry layer failed: {_0}")]
234 Layer(LayerError<ExtractError<E>>),
235 #[display("entry extraction failed: {_0}")]
236 Finish(FinishError),
237}
238
239pub struct LayerApplier<'decoder, 'archive> {
241 decoder: VerifiedDecoder<'decoder>,
242 archive: Archive<'archive>,
243 state: ApplyState,
244}
245
246#[derive(Clone, Copy, Debug, Eq, PartialEq)]
247enum ApplyState {
248 Ready,
249 Applying,
250 Failed,
251 Finished,
252}
253
254impl<'decoder, 'archive> LayerApplier<'decoder, 'archive> {
255 pub fn new(decoder: VerifiedDecoder<'decoder>, archive: Archive<'archive>) -> Self {
256 Self {
257 decoder,
258 archive,
259 state: ApplyState::Ready,
260 }
261 }
262
263 pub fn push<S: TransactionalLayerSink>(
264 &mut self,
265 input: &[u8],
266 sink: &mut S,
267 ) -> Result<(), ApplyError<S::Error>> {
268 match self.state {
269 ApplyState::Ready => {
270 sink.begin_layer().map_err(ApplyError::Sink)?;
271 self.state = ApplyState::Applying;
272 }
273 ApplyState::Applying => {}
274 ApplyState::Failed | ApplyState::Finished => return Err(ApplyError::InvalidState),
275 }
276 let (decoder, archive) = (&mut self.decoder, &mut self.archive);
277 let result = decoder.push(input, |bytes| archive.push(bytes, sink));
278 if let Err(error) = result {
279 sink.abort_layer();
280 self.state = ApplyState::Failed;
281 return Err(map_apply_error(error));
282 }
283 Ok(())
284 }
285
286 pub fn finish<S: TransactionalLayerSink>(
287 &mut self,
288 sink: &mut S,
289 ) -> Result<(), ApplyError<S::Error>> {
290 if self.state == ApplyState::Ready {
291 sink.begin_layer().map_err(ApplyError::Sink)?;
292 self.state = ApplyState::Applying;
293 }
294 if self.state != ApplyState::Applying {
295 return Err(ApplyError::InvalidState);
296 }
297 let (decoder, archive) = (&mut self.decoder, &mut self.archive);
298 if let Err(error) = decoder.finish(|bytes| archive.push(bytes, sink)) {
299 sink.abort_layer();
300 self.state = ApplyState::Failed;
301 return Err(map_apply_error(error));
302 }
303 if let Err(error) = archive.finish() {
304 sink.abort_layer();
305 self.state = ApplyState::Failed;
306 return Err(ApplyError::ArchiveFinish(error));
307 }
308 if let Err(error) = sink.commit_layer() {
309 sink.abort_layer();
310 self.state = ApplyState::Failed;
311 return Err(ApplyError::Sink(error));
312 }
313 self.state = ApplyState::Finished;
314 Ok(())
315 }
316}
317
318#[derive(Debug, derive_more::Display)]
319pub enum ApplyError<E> {
320 #[display("layer format error: {_0}")]
321 Format(LayerFormatError),
322 #[display("layer integrity error: {_0}")]
323 Integrity(VerifyError),
324 #[display("layer archive error: {_0}")]
325 Archive(ArchiveError<E>),
326 #[display("layer archive error: {_0}")]
327 ArchiveFinish(ArchiveFinishError),
328 #[display("layer sink failed: {_0}")]
329 Sink(E),
330 #[display("invalid layer application state")]
331 InvalidState,
332}
333
334fn map_apply_error<E>(error: LayerError<ArchiveError<E>>) -> ApplyError<E> {
335 match error {
336 LayerError::Format(error) => ApplyError::Format(error),
337 LayerError::Integrity(error) => ApplyError::Integrity(error),
338 LayerError::Output(error) => ApplyError::Archive(error),
339 }
340}
341
342impl<'a> VerifiedDecoder<'a> {
343 pub fn new(
344 decoder: Decoder<'a>,
345 compressed_digest: Digest,
346 compressed_size: u64,
347 diff_id: Digest,
348 ) -> Self {
349 Self {
350 decoder,
351 compressed: Verifier::new(compressed_digest, compressed_size),
352 uncompressed: Some(Verifier::digest_only(diff_id)),
353 decompressed_size: 0,
354 }
355 }
356
357 pub fn compressed_only(
363 decoder: Decoder<'a>,
364 compressed_digest: Digest,
365 compressed_size: u64,
366 ) -> Self {
367 Self {
368 decoder,
369 compressed: Verifier::new(compressed_digest, compressed_size),
370 uncompressed: None,
371 decompressed_size: 0,
372 }
373 }
374
375 pub fn push<E>(
376 &mut self,
377 input: &[u8],
378 mut output: impl FnMut(&[u8]) -> Result<(), E>,
379 ) -> Result<(), LayerError<E>> {
380 self.compressed
381 .update(input)
382 .map_err(LayerError::Integrity)?;
383 let mut remaining = input;
384 let mut turns_without_bytes = 0usize;
385 while !remaining.is_empty() {
386 let step = self.decoder.decode(remaining).map_err(LayerError::Format)?;
387 let consumed = step.consumed();
388 if let DecodeStep::Output { bytes, .. } = step {
389 self.decompressed_size += bytes.len() as u64;
390 if let Some(uncompressed) = &mut self.uncompressed {
391 uncompressed.update(bytes).map_err(LayerError::Integrity)?;
392 }
393 output(bytes).map_err(LayerError::Output)?;
394 }
395 if consumed == 0 {
396 turns_without_bytes += 1;
397 if turns_without_bytes > 16 {
398 return Err(LayerError::Format(LayerFormatError::DecoderStalled));
399 }
400 } else {
401 turns_without_bytes = 0;
402 remaining = &remaining[consumed..];
403 }
404 }
405 Ok(())
406 }
407
408 pub const fn compressed_size(&self) -> u64 {
409 self.compressed.actual_size()
410 }
411
412 pub const fn decompressed_size(&self) -> u64 {
413 self.decompressed_size
414 }
415
416 pub fn finish<E>(
417 &mut self,
418 mut output: impl FnMut(&[u8]) -> Result<(), E>,
419 ) -> Result<(), LayerError<E>> {
420 loop {
421 match self.decoder.decode(&[]).map_err(LayerError::Format)? {
422 DecodeStep::Output { bytes, .. } => {
423 self.decompressed_size += bytes.len() as u64;
424 if let Some(uncompressed) = &mut self.uncompressed {
425 uncompressed.update(bytes).map_err(LayerError::Integrity)?;
426 }
427 output(bytes).map_err(LayerError::Output)?;
428 }
429 DecodeStep::Progress { .. } => {}
430 DecodeStep::NeedInput { .. } => break,
431 }
432 }
433 self.decoder.finish().map_err(LayerError::Format)?;
434 self.compressed.finish().map_err(LayerError::Integrity)?;
435 if let Some(uncompressed) = &self.uncompressed {
436 uncompressed.finish().map_err(LayerError::Integrity)?;
437 }
438 Ok(())
439 }
440}
441
442#[derive(Debug, derive_more::Display)]
443pub enum LayerError<E> {
444 #[display("layer format error: {_0}")]
445 Format(LayerFormatError),
446 #[display("layer integrity error: {_0}")]
447 Integrity(VerifyError),
448 #[display("layer output error: {_0}")]
449 Output(E),
450}
451
452#[derive(Debug, derive_more::Display)]
453pub enum LayerFormatError {
454 #[display("unsupported OCI layer media type")]
455 UnsupportedMediaType,
456 #[display("{_0:?} layer support is disabled")]
457 EncodingDisabled(Encoding),
458 #[display("layer decoder stopped making progress")]
459 DecoderStalled,
460 #[cfg(feature = "gzip")]
461 #[display("{_0}")]
462 Gzip(gzip_zero::DecodeError),
463 #[cfg(feature = "zstd")]
464 #[display("{_0}")]
465 Zstd(zstd_zero::DecodeError),
466}
467
468#[cfg(test)]
469mod tests {
470 use sha2::{Digest as _, Sha256};
471 use std::string::ToString;
472
473 use super::{
474 encoding, ApplyError, Decoder, Encoding, EntryLayerError, LayerApplier, LayerError,
475 LayerFormatError, VerifiedDecoder, VerifiedEntryExtractor, DOCKER_FOREIGN_LAYER_GZIP,
476 DOCKER_LAYER_GZIP, DOCKER_LAYER_TAR, OCI_LAYER_GZIP, OCI_LAYER_TAR, OCI_LAYER_ZSTD,
477 OCI_NONDISTRIBUTABLE_GZIP, OCI_NONDISTRIBUTABLE_TAR, OCI_NONDISTRIBUTABLE_ZSTD,
478 };
479 use crate::{
480 digest::Digest,
481 tar::{
482 Archive, ArchiveBuffers, Entry, FinishError, LayerEventSink, TransactionalLayerSink,
483 },
484 };
485
486 #[derive(Default)]
487 struct TransactionSink {
488 began: bool,
489 committed: bool,
490 aborted: bool,
491 }
492
493 impl LayerEventSink for TransactionSink {
494 type Error = ();
495
496 fn begin_entry(&mut self, _entry: Entry<'_>) -> Result<(), Self::Error> {
497 Ok(())
498 }
499
500 fn entry_data(&mut self, _bytes: &[u8]) -> Result<(), Self::Error> {
501 Ok(())
502 }
503
504 fn end_entry(&mut self) -> Result<(), Self::Error> {
505 Ok(())
506 }
507
508 fn whiteout(&mut self, _path: &[u8]) -> Result<(), Self::Error> {
509 Ok(())
510 }
511
512 fn opaque_directory(&mut self, _path: &[u8]) -> Result<(), Self::Error> {
513 Ok(())
514 }
515 }
516
517 impl TransactionalLayerSink for TransactionSink {
518 fn begin_layer(&mut self) -> Result<(), Self::Error> {
519 self.began = true;
520 Ok(())
521 }
522
523 fn commit_layer(&mut self) -> Result<(), Self::Error> {
524 self.committed = true;
525 Ok(())
526 }
527
528 fn abort_layer(&mut self) {
529 self.aborted = true;
530 }
531 }
532
533 #[test]
534 fn recognizes_every_supported_layer_media_type() {
535 for media_type in [OCI_LAYER_TAR, OCI_NONDISTRIBUTABLE_TAR, DOCKER_LAYER_TAR] {
536 assert_eq!(encoding(media_type).unwrap(), Encoding::Tar);
537 }
538 for media_type in [
539 OCI_LAYER_GZIP,
540 OCI_NONDISTRIBUTABLE_GZIP,
541 DOCKER_LAYER_GZIP,
542 DOCKER_FOREIGN_LAYER_GZIP,
543 ] {
544 assert_eq!(encoding(media_type).unwrap(), Encoding::Gzip);
545 }
546 for media_type in [OCI_LAYER_ZSTD, OCI_NONDISTRIBUTABLE_ZSTD] {
547 assert_eq!(encoding(media_type).unwrap(), Encoding::Zstd);
548 }
549 assert!(matches!(
550 encoding("application/octet-stream"),
551 Err(LayerFormatError::UnsupportedMediaType)
552 ));
553 }
554
555 #[test]
556 fn formats_layer_errors() {
557 assert_eq!(
558 EntryLayerError::<&str>::Finish(FinishError::NotFound).to_string(),
559 "entry extraction failed: tar entry not found"
560 );
561 assert_eq!(
562 ApplyError::<&str>::InvalidState.to_string(),
563 "invalid layer application state"
564 );
565 assert_eq!(
566 LayerError::Output("callback").to_string(),
567 "layer output error: callback"
568 );
569 assert_eq!(
570 LayerFormatError::UnsupportedMediaType.to_string(),
571 "unsupported OCI layer media type"
572 );
573 }
574
575 #[test]
576 fn verifies_uncompressed_layers() {
577 let bytes = b"tar bytes";
578 let digest = Digest::from_bytes(Sha256::digest(bytes).into());
579 let mut decoder = VerifiedDecoder::new(Decoder::tar(), digest, bytes.len() as u64, digest);
580 assert_eq!(decoder.compressed_size(), 0);
581 let mut output = [0; 9];
582 let mut length = 0;
583 decoder
584 .push(bytes, |chunk| {
585 output[length..length + chunk.len()].copy_from_slice(chunk);
586 length += chunk.len();
587 Ok::<_, ()>(())
588 })
589 .unwrap();
590 assert_eq!(decoder.compressed_size(), bytes.len() as u64);
591 decoder.finish(|_| Ok::<_, ()>(())).unwrap();
592 assert_eq!(&output, bytes);
593 }
594
595 #[test]
596 fn compressed_only_accepts_content_without_a_diff_id() {
597 let bytes = b"artifact contents";
598 let digest = Digest::from_bytes(Sha256::digest(bytes).into());
599 let mut decoder =
600 VerifiedDecoder::compressed_only(Decoder::tar(), digest, bytes.len() as u64);
601 decoder.push(bytes, |_| Ok::<_, ()>(())).unwrap();
602 decoder.finish(|_| Ok::<_, ()>(())).unwrap();
603 assert_eq!(decoder.decompressed_size(), bytes.len() as u64);
604 }
605
606 #[test]
607 fn compressed_only_still_verifies_descriptor_digest_and_size() {
608 let bytes = b"artifact contents";
609 let digest = Digest::from_bytes(Sha256::digest(bytes).into());
610 let mut wrong_digest = VerifiedDecoder::compressed_only(
611 Decoder::tar(),
612 Digest::from_bytes([9; 32]),
613 bytes.len() as u64,
614 );
615 wrong_digest.push(bytes, |_| Ok::<_, ()>(())).unwrap();
616 assert!(wrong_digest.finish(|_| Ok::<_, ()>(())).is_err());
617
618 let mut wrong_size = VerifiedDecoder::compressed_only(Decoder::tar(), digest, 1);
619 wrong_size.push(bytes, |_| Ok::<_, ()>(())).unwrap();
620 assert!(wrong_size.finish(|_| Ok::<_, ()>(())).is_err());
621 }
622
623 #[test]
624 fn verifies_and_extracts_one_entry() {
625 let mut tar = [0u8; 2048];
626 tar[..6].copy_from_slice(b"wanted");
627 write_octal(&mut tar[100..108], 0o644);
628 write_octal(&mut tar[124..136], 5);
629 tar[148..156].fill(b' ');
630 tar[156] = b'0';
631 let checksum = tar[..512].iter().map(|byte| u64::from(*byte)).sum();
632 write_octal(&mut tar[148..156], checksum);
633 tar[512..517].copy_from_slice(b"hello");
634
635 let digest = Digest::from_bytes(Sha256::digest(tar).into());
636 let decoder = VerifiedDecoder::new(Decoder::tar(), digest, tar.len() as u64, digest);
637 let mut extractor = VerifiedEntryExtractor::new(decoder, b"wanted");
638 assert!(!extractor.found());
639 assert_eq!(extractor.compressed_size(), 0);
640 let mut output = [0u8; 5];
641 let mut length = 0;
642 for fragment in tar.chunks(7) {
643 extractor
644 .push(fragment, |bytes| {
645 output[length..length + bytes.len()].copy_from_slice(bytes);
646 length += bytes.len();
647 Ok::<_, ()>(())
648 })
649 .unwrap();
650 }
651 assert!(extractor.found());
652 assert_eq!(extractor.compressed_size(), tar.len() as u64);
653 extractor.finish(|_| Ok::<_, ()>(())).unwrap();
654 assert_eq!(&output, b"hello");
655 assert_eq!(extractor.decompressed_size(), tar.len() as u64);
656 assert_eq!(extractor.extracted_size(), 5);
657 }
658
659 #[test]
660 fn entry_extractor_finish_reports_a_missing_target() {
661 let tar = [0u8; 1024];
662 let digest = Digest::from_bytes(Sha256::digest(tar).into());
663 let decoder = VerifiedDecoder::new(Decoder::tar(), digest, tar.len() as u64, digest);
664 let mut extractor = VerifiedEntryExtractor::new(decoder, b"missing");
665 extractor.push(&tar, |_| Ok::<_, ()>(())).unwrap();
666 assert!(matches!(
667 extractor.finish(|_| Ok::<_, ()>(())),
668 Err(EntryLayerError::Finish(FinishError::NotFound))
669 ));
670 }
671
672 fn write_octal(field: &mut [u8], value: u64) {
673 field.fill(b'0');
674 let digits = field.len() - 1;
675 field[digits] = 0;
676 let mut value = value;
677 for byte in field[..digits].iter_mut().rev() {
678 *byte = b'0' + (value & 7) as u8;
679 value >>= 3;
680 }
681 }
682
683 #[test]
684 fn commits_only_after_archive_and_integrity_finish() {
685 let tar = [0; 1024];
686 let digest = Digest::from_bytes(Sha256::digest(tar).into());
687 let mut path = [0; 64];
688 let mut link = [0; 64];
689 let mut pax = [0; 64];
690 let archive = Archive::new(ArchiveBuffers {
691 path: &mut path,
692 link: &mut link,
693 pax: &mut pax,
694 });
695 let decoder = VerifiedDecoder::new(Decoder::tar(), digest, tar.len() as u64, digest);
696 let mut applier = LayerApplier::new(decoder, archive);
697 let mut sink = TransactionSink::default();
698 applier.push(&tar, &mut sink).unwrap();
699 assert!(sink.began);
700 assert!(!sink.committed);
701 applier.finish(&mut sink).unwrap();
702 assert!(sink.committed);
703 assert!(!sink.aborted);
704 }
705
706 #[test]
707 fn finish_starts_a_ready_transaction_before_reporting_failure() {
708 let digest = Digest::from_bytes(Sha256::digest([]).into());
709 let mut path = [0; 64];
710 let mut link = [0; 64];
711 let mut pax = [0; 64];
712 let archive = Archive::new(ArchiveBuffers {
713 path: &mut path,
714 link: &mut link,
715 pax: &mut pax,
716 });
717 let decoder = VerifiedDecoder::new(Decoder::tar(), digest, 0, digest);
718 let mut applier = LayerApplier::new(decoder, archive);
719 let mut sink = TransactionSink::default();
720
721 assert!(applier.finish(&mut sink).is_err());
722 assert!(sink.began);
723 assert!(sink.aborted);
724 assert!(!sink.committed);
725 }
726
727 #[test]
728 fn aborts_when_integrity_fails() {
729 let tar = [0; 1024];
730 let diff_id = Digest::from_bytes(Sha256::digest(tar).into());
731 let mut path = [0; 64];
732 let mut link = [0; 64];
733 let mut pax = [0; 64];
734 let archive = Archive::new(ArchiveBuffers {
735 path: &mut path,
736 link: &mut link,
737 pax: &mut pax,
738 });
739 let decoder = VerifiedDecoder::new(
740 Decoder::tar(),
741 Digest::from_bytes([1; 32]),
742 tar.len() as u64,
743 diff_id,
744 );
745 let mut applier = LayerApplier::new(decoder, archive);
746 let mut sink = TransactionSink::default();
747 applier.push(&tar, &mut sink).unwrap();
748 assert!(applier.finish(&mut sink).is_err());
749 assert!(sink.aborted);
750 assert!(!sink.committed);
751 }
752
753 #[cfg(feature = "gzip")]
754 #[test]
755 fn verifies_gzip_layers() {
756 const ENCODED: &[u8] = &[
757 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9,
758 0xc9, 0x07, 0x00, 0x86, 0xa6, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00,
759 ];
760 let compressed = Digest::from_bytes(Sha256::digest(ENCODED).into());
761 let diff_id = Digest::from_bytes(Sha256::digest(b"hello").into());
762 let mut history = [0; gzip_zero::HISTORY_SIZE];
763 let decoder = Decoder::gzip(gzip_zero::DecoderBuffers {
764 history: &mut history,
765 })
766 .unwrap();
767 let mut decoder = VerifiedDecoder::new(decoder, compressed, ENCODED.len() as u64, diff_id);
768 let mut output = [0; 5];
769 let mut length = 0;
770 for byte in ENCODED {
771 decoder
772 .push(core::slice::from_ref(byte), |chunk| {
773 output[length..length + chunk.len()].copy_from_slice(chunk);
774 length += chunk.len();
775 Ok::<_, ()>(())
776 })
777 .unwrap();
778 }
779 decoder.finish(|_| Ok::<_, ()>(())).unwrap();
780 assert_eq!(&output, b"hello");
781 }
782
783 #[cfg(feature = "gzip")]
784 #[test]
785 fn gzip_decoder_finish_rejects_a_truncated_header() {
786 let mut history = [0; gzip_zero::HISTORY_SIZE];
787 let mut decoder = Decoder::gzip(gzip_zero::DecoderBuffers {
788 history: &mut history,
789 })
790 .unwrap();
791 decoder.decode(&[0x1f]).unwrap();
792 assert!(decoder.finish().is_err());
793 }
794
795 #[cfg(feature = "zstd")]
796 #[test]
797 fn verifies_zstd_layers() {
798 const ENCODED: &[u8] = &[
799 0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x05, 0x29, 0, 0, b'h', b'e', b'l', b'l', b'o',
800 ];
801 let compressed = Digest::from_bytes(Sha256::digest(ENCODED).into());
802 let diff_id = Digest::from_bytes(Sha256::digest(b"hello").into());
803 let mut history = [0; 5];
804 let mut block = [0; 5];
805 let mut literals = [0; 5];
806 let mut fse_scratch = [0i16; zstd_zero::FSE_SCRATCH_LEN];
807 let mut fse = [zstd_zero::FseEntry::new(); zstd_zero::FSE_ENTRIES];
808 let mut huffman = [zstd_zero::HuffmanEntry::new(); zstd_zero::HUFFMAN_ENTRIES];
809 let decoder = Decoder::zstd(zstd_zero::DecoderBuffers {
810 history: &mut history,
811 block: &mut block,
812 literals: &mut literals,
813 fse_scratch: &mut fse_scratch,
814 fse: &mut fse,
815 huffman: &mut huffman,
816 })
817 .unwrap();
818 let mut decoder = VerifiedDecoder::new(decoder, compressed, ENCODED.len() as u64, diff_id);
819 let mut output = [0; 5];
820 let mut length = 0;
821 decoder
822 .push(ENCODED, |chunk| {
823 output[length..length + chunk.len()].copy_from_slice(chunk);
824 length += chunk.len();
825 Ok::<_, ()>(())
826 })
827 .unwrap();
828 decoder.finish(|_| Ok::<_, ()>(())).unwrap();
829 assert_eq!(&output, b"hello");
830 }
831
832 #[cfg(feature = "zstd")]
833 #[test]
834 fn finish_accounts_for_buffered_zstd_output() {
835 let encoded = [
838 0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x00, 0x0a, 0x00, 0x00, b'a', 0x03, 0x20, 0x00, b'b', ];
842 let mut decoded = std::vec![b'b'; 1025];
843 decoded[0] = b'a';
844 let compressed = Digest::from_bytes(Sha256::digest(encoded).into());
845 let diff_id = Digest::from_bytes(Sha256::digest(&decoded).into());
846 let mut history = [0; 1024];
847 let mut block = [0; 1];
848 let mut literals = [];
849 let mut fse_scratch = [0i16; zstd_zero::FSE_SCRATCH_LEN];
850 let mut fse = [zstd_zero::FseEntry::new(); zstd_zero::FSE_ENTRIES];
851 let mut huffman = [zstd_zero::HuffmanEntry::new(); zstd_zero::HUFFMAN_ENTRIES];
852 let decoder = Decoder::zstd(zstd_zero::DecoderBuffers {
853 history: &mut history,
854 block: &mut block,
855 literals: &mut literals,
856 fse_scratch: &mut fse_scratch,
857 fse: &mut fse,
858 huffman: &mut huffman,
859 })
860 .unwrap();
861 let mut decoder = VerifiedDecoder::new(decoder, compressed, encoded.len() as u64, diff_id);
862 let mut output = std::vec::Vec::new();
863
864 decoder
865 .push(&encoded, |bytes| {
866 output.extend_from_slice(bytes);
867 Ok::<_, ()>(())
868 })
869 .unwrap();
870 assert_eq!(decoder.decompressed_size(), 1024);
871 decoder
872 .finish(|bytes| {
873 output.extend_from_slice(bytes);
874 Ok::<_, ()>(())
875 })
876 .unwrap();
877
878 assert_eq!(decoder.decompressed_size(), 1025);
879 assert_eq!(output, decoded);
880 }
881
882 #[cfg(feature = "zstd")]
883 #[test]
884 fn zstd_decoder_finish_rejects_a_truncated_header() {
885 let mut history = [0; 1];
886 let mut block = [0; 1];
887 let mut literals = [0; 1];
888 let mut fse_scratch = [0i16; zstd_zero::FSE_SCRATCH_LEN];
889 let mut fse = [zstd_zero::FseEntry::new(); zstd_zero::FSE_ENTRIES];
890 let mut huffman = [zstd_zero::HuffmanEntry::new(); zstd_zero::HUFFMAN_ENTRIES];
891 let mut decoder = Decoder::zstd(zstd_zero::DecoderBuffers {
892 history: &mut history,
893 block: &mut block,
894 literals: &mut literals,
895 fse_scratch: &mut fse_scratch,
896 fse: &mut fse,
897 huffman: &mut huffman,
898 })
899 .unwrap();
900 decoder.decode(&[0x28]).unwrap();
901 assert!(decoder.finish().is_err());
902 }
903}