Skip to main content

oci_zero/
layer.rs

1//! Verified, allocation-free OCI layer decoding.
2
3use core::fmt;
4
5use crate::{
6    digest::{Digest, Verifier, VerifyError},
7    tar::{
8        Archive, ArchiveError, ArchiveFinishError, EntryExtractor, ExtractError, FinishError,
9        TransactionalLayerSink,
10    },
11};
12
13pub const OCI_LAYER_TAR: &str = "application/vnd.oci.image.layer.v1.tar";
14pub const OCI_LAYER_GZIP: &str = "application/vnd.oci.image.layer.v1.tar+gzip";
15pub const OCI_LAYER_ZSTD: &str = "application/vnd.oci.image.layer.v1.tar+zstd";
16pub const OCI_NONDISTRIBUTABLE_TAR: &str =
17    "application/vnd.oci.image.layer.nondistributable.v1.tar";
18pub const OCI_NONDISTRIBUTABLE_GZIP: &str =
19    "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip";
20pub const OCI_NONDISTRIBUTABLE_ZSTD: &str =
21    "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd";
22pub const DOCKER_LAYER_GZIP: &str = "application/vnd.docker.image.rootfs.diff.tar.gzip";
23pub const DOCKER_FOREIGN_LAYER_GZIP: &str =
24    "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip";
25pub const DOCKER_LAYER_TAR: &str = "application/vnd.docker.image.rootfs.diff.tar";
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum Encoding {
29    Tar,
30    Gzip,
31    Zstd,
32}
33
34pub fn encoding(media_type: &str) -> Result<Encoding, LayerFormatError> {
35    match media_type {
36        OCI_LAYER_TAR | OCI_NONDISTRIBUTABLE_TAR | DOCKER_LAYER_TAR => Ok(Encoding::Tar),
37        OCI_LAYER_GZIP
38        | OCI_NONDISTRIBUTABLE_GZIP
39        | DOCKER_LAYER_GZIP
40        | DOCKER_FOREIGN_LAYER_GZIP => Ok(Encoding::Gzip),
41        OCI_LAYER_ZSTD | OCI_NONDISTRIBUTABLE_ZSTD => Ok(Encoding::Zstd),
42        _ => Err(LayerFormatError::UnsupportedMediaType),
43    }
44}
45
46// Keeping codec state inline is intentional: indirection would require an
47// allocator or a second caller-owned object with a more fragile lifetime API.
48#[allow(clippy::large_enum_variant)]
49pub enum Decoder<'a> {
50    Tar,
51    #[cfg(feature = "gzip")]
52    Gzip(gzip_zero::Decoder<'a>),
53    #[cfg(feature = "zstd")]
54    Zstd(zstd_zero::Decoder<'a>),
55    #[doc(hidden)]
56    _Lifetime(core::marker::PhantomData<&'a mut [u8]>),
57}
58
59impl<'a> Decoder<'a> {
60    pub const fn tar() -> Self {
61        Self::Tar
62    }
63
64    #[cfg(feature = "gzip")]
65    pub fn gzip(buffers: gzip_zero::DecoderBuffers<'a>) -> Result<Self, LayerFormatError> {
66        Ok(Self::Gzip(
67            gzip_zero::Decoder::new(buffers).map_err(LayerFormatError::Gzip)?,
68        ))
69    }
70
71    #[cfg(feature = "zstd")]
72    pub fn zstd(buffers: zstd_zero::DecoderBuffers<'a>) -> Self {
73        Self::Zstd(zstd_zero::Decoder::new(buffers))
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
159/// Verifies and decodes a layer while extracting one regular tar entry.
160pub 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)]
232pub enum EntryLayerError<E> {
233    Layer(LayerError<ExtractError<E>>),
234    Finish(FinishError),
235}
236
237impl<E: fmt::Display> fmt::Display for EntryLayerError<E> {
238    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
239        match self {
240            Self::Layer(error) => write!(formatter, "entry layer failed: {error}"),
241            Self::Finish(error) => write!(formatter, "entry extraction failed: {error}"),
242        }
243    }
244}
245
246/// Transactionally decodes, verifies, parses, and applies one OCI layer.
247pub struct LayerApplier<'decoder, 'archive> {
248    decoder: VerifiedDecoder<'decoder>,
249    archive: Archive<'archive>,
250    state: ApplyState,
251}
252
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
254enum ApplyState {
255    Ready,
256    Applying,
257    Failed,
258    Finished,
259}
260
261impl<'decoder, 'archive> LayerApplier<'decoder, 'archive> {
262    pub fn new(decoder: VerifiedDecoder<'decoder>, archive: Archive<'archive>) -> Self {
263        Self {
264            decoder,
265            archive,
266            state: ApplyState::Ready,
267        }
268    }
269
270    pub fn push<S: TransactionalLayerSink>(
271        &mut self,
272        input: &[u8],
273        sink: &mut S,
274    ) -> Result<(), ApplyError<S::Error>> {
275        match self.state {
276            ApplyState::Ready => {
277                sink.begin_layer().map_err(ApplyError::Sink)?;
278                self.state = ApplyState::Applying;
279            }
280            ApplyState::Applying => {}
281            ApplyState::Failed | ApplyState::Finished => return Err(ApplyError::InvalidState),
282        }
283        let (decoder, archive) = (&mut self.decoder, &mut self.archive);
284        let result = decoder.push(input, |bytes| archive.push(bytes, sink));
285        if let Err(error) = result {
286            sink.abort_layer();
287            self.state = ApplyState::Failed;
288            return Err(map_apply_error(error));
289        }
290        Ok(())
291    }
292
293    pub fn finish<S: TransactionalLayerSink>(
294        &mut self,
295        sink: &mut S,
296    ) -> Result<(), ApplyError<S::Error>> {
297        if self.state == ApplyState::Ready {
298            sink.begin_layer().map_err(ApplyError::Sink)?;
299            self.state = ApplyState::Applying;
300        }
301        if self.state != ApplyState::Applying {
302            return Err(ApplyError::InvalidState);
303        }
304        let (decoder, archive) = (&mut self.decoder, &mut self.archive);
305        if let Err(error) = decoder.finish(|bytes| archive.push(bytes, sink)) {
306            sink.abort_layer();
307            self.state = ApplyState::Failed;
308            return Err(map_apply_error(error));
309        }
310        if let Err(error) = archive.finish() {
311            sink.abort_layer();
312            self.state = ApplyState::Failed;
313            return Err(ApplyError::ArchiveFinish(error));
314        }
315        if let Err(error) = sink.commit_layer() {
316            sink.abort_layer();
317            self.state = ApplyState::Failed;
318            return Err(ApplyError::Sink(error));
319        }
320        self.state = ApplyState::Finished;
321        Ok(())
322    }
323}
324
325#[derive(Debug)]
326pub enum ApplyError<E> {
327    Format(LayerFormatError),
328    Integrity(VerifyError),
329    Archive(ArchiveError<E>),
330    ArchiveFinish(ArchiveFinishError),
331    Sink(E),
332    InvalidState,
333}
334
335impl<E: fmt::Display> fmt::Display for ApplyError<E> {
336    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
337        match self {
338            Self::Format(error) => write!(formatter, "layer format error: {error}"),
339            Self::Integrity(error) => write!(formatter, "layer integrity error: {error}"),
340            Self::Archive(error) => write!(formatter, "layer archive error: {error}"),
341            Self::ArchiveFinish(error) => write!(formatter, "layer archive error: {error}"),
342            Self::Sink(error) => write!(formatter, "layer sink failed: {error}"),
343            Self::InvalidState => formatter.write_str("invalid layer application state"),
344        }
345    }
346}
347
348fn map_apply_error<E>(error: LayerError<ArchiveError<E>>) -> ApplyError<E> {
349    match error {
350        LayerError::Format(error) => ApplyError::Format(error),
351        LayerError::Integrity(error) => ApplyError::Integrity(error),
352        LayerError::Output(error) => ApplyError::Archive(error),
353    }
354}
355
356impl<'a> VerifiedDecoder<'a> {
357    pub fn new(
358        decoder: Decoder<'a>,
359        compressed_digest: Digest,
360        compressed_size: u64,
361        diff_id: Digest,
362    ) -> Self {
363        Self {
364            decoder,
365            compressed: Verifier::new(compressed_digest, compressed_size),
366            uncompressed: Some(Verifier::digest_only(diff_id)),
367            decompressed_size: 0,
368        }
369    }
370
371    /// Creates a decoder that verifies the compressed descriptor only.
372    ///
373    /// OCI artifacts commonly use layer archives without an image config's
374    /// `rootfs.diff_ids`. Decoded byte accounting remains available, but no
375    /// uncompressed digest is expected or checked.
376    pub fn compressed_only(
377        decoder: Decoder<'a>,
378        compressed_digest: Digest,
379        compressed_size: u64,
380    ) -> Self {
381        Self {
382            decoder,
383            compressed: Verifier::new(compressed_digest, compressed_size),
384            uncompressed: None,
385            decompressed_size: 0,
386        }
387    }
388
389    pub fn push<E>(
390        &mut self,
391        input: &[u8],
392        mut output: impl FnMut(&[u8]) -> Result<(), E>,
393    ) -> Result<(), LayerError<E>> {
394        self.compressed
395            .update(input)
396            .map_err(LayerError::Integrity)?;
397        let mut remaining = input;
398        let mut turns_without_bytes = 0usize;
399        while !remaining.is_empty() {
400            let step = self.decoder.decode(remaining).map_err(LayerError::Format)?;
401            let consumed = step.consumed();
402            if let DecodeStep::Output { bytes, .. } = step {
403                self.decompressed_size += bytes.len() as u64;
404                if let Some(uncompressed) = &mut self.uncompressed {
405                    uncompressed.update(bytes).map_err(LayerError::Integrity)?;
406                }
407                output(bytes).map_err(LayerError::Output)?;
408            }
409            if consumed == 0 {
410                turns_without_bytes += 1;
411                if turns_without_bytes > 16 {
412                    return Err(LayerError::Format(LayerFormatError::DecoderStalled));
413                }
414            } else {
415                turns_without_bytes = 0;
416                remaining = &remaining[consumed..];
417            }
418        }
419        Ok(())
420    }
421
422    pub const fn compressed_size(&self) -> u64 {
423        self.compressed.actual_size()
424    }
425
426    pub const fn decompressed_size(&self) -> u64 {
427        self.decompressed_size
428    }
429
430    pub fn finish<E>(
431        &mut self,
432        mut output: impl FnMut(&[u8]) -> Result<(), E>,
433    ) -> Result<(), LayerError<E>> {
434        loop {
435            match self.decoder.decode(&[]).map_err(LayerError::Format)? {
436                DecodeStep::Output { bytes, .. } => {
437                    self.decompressed_size += bytes.len() as u64;
438                    if let Some(uncompressed) = &mut self.uncompressed {
439                        uncompressed.update(bytes).map_err(LayerError::Integrity)?;
440                    }
441                    output(bytes).map_err(LayerError::Output)?;
442                }
443                DecodeStep::Progress { .. } => {}
444                DecodeStep::NeedInput { .. } => break,
445            }
446        }
447        self.decoder.finish().map_err(LayerError::Format)?;
448        self.compressed.finish().map_err(LayerError::Integrity)?;
449        if let Some(uncompressed) = &self.uncompressed {
450            uncompressed.finish().map_err(LayerError::Integrity)?;
451        }
452        Ok(())
453    }
454}
455
456#[derive(Debug)]
457pub enum LayerError<E> {
458    Format(LayerFormatError),
459    Integrity(VerifyError),
460    Output(E),
461}
462
463impl<E: fmt::Display> fmt::Display for LayerError<E> {
464    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
465        match self {
466            Self::Format(error) => write!(formatter, "layer format error: {error}"),
467            Self::Integrity(error) => write!(formatter, "layer integrity error: {error}"),
468            Self::Output(error) => write!(formatter, "layer output error: {error}"),
469        }
470    }
471}
472
473#[derive(Debug)]
474pub enum LayerFormatError {
475    UnsupportedMediaType,
476    EncodingDisabled(Encoding),
477    DecoderStalled,
478    #[cfg(feature = "gzip")]
479    Gzip(gzip_zero::DecodeError),
480    #[cfg(feature = "zstd")]
481    Zstd(zstd_zero::DecodeError),
482}
483
484impl fmt::Display for LayerFormatError {
485    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
486        match self {
487            Self::UnsupportedMediaType => formatter.write_str("unsupported OCI layer media type"),
488            Self::EncodingDisabled(encoding) => {
489                write!(formatter, "{encoding:?} layer support is disabled")
490            }
491            Self::DecoderStalled => formatter.write_str("layer decoder stopped making progress"),
492            #[cfg(feature = "gzip")]
493            Self::Gzip(error) => write!(formatter, "{error}"),
494            #[cfg(feature = "zstd")]
495            Self::Zstd(error) => write!(formatter, "{error}"),
496        }
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use sha2::{Digest as _, Sha256};
503
504    use super::{Decoder, LayerApplier, VerifiedDecoder, VerifiedEntryExtractor};
505    use crate::{
506        digest::Digest,
507        tar::{Archive, ArchiveBuffers, Entry, LayerEventSink, TransactionalLayerSink},
508    };
509
510    #[derive(Default)]
511    struct TransactionSink {
512        began: bool,
513        committed: bool,
514        aborted: bool,
515    }
516
517    impl LayerEventSink for TransactionSink {
518        type Error = ();
519
520        fn begin_entry(&mut self, _entry: Entry<'_>) -> Result<(), Self::Error> {
521            Ok(())
522        }
523
524        fn entry_data(&mut self, _bytes: &[u8]) -> Result<(), Self::Error> {
525            Ok(())
526        }
527
528        fn end_entry(&mut self) -> Result<(), Self::Error> {
529            Ok(())
530        }
531
532        fn whiteout(&mut self, _path: &[u8]) -> Result<(), Self::Error> {
533            Ok(())
534        }
535
536        fn opaque_directory(&mut self, _path: &[u8]) -> Result<(), Self::Error> {
537            Ok(())
538        }
539    }
540
541    impl TransactionalLayerSink for TransactionSink {
542        fn begin_layer(&mut self) -> Result<(), Self::Error> {
543            self.began = true;
544            Ok(())
545        }
546
547        fn commit_layer(&mut self) -> Result<(), Self::Error> {
548            self.committed = true;
549            Ok(())
550        }
551
552        fn abort_layer(&mut self) {
553            self.aborted = true;
554        }
555    }
556
557    #[test]
558    fn verifies_uncompressed_layers() {
559        let bytes = b"tar bytes";
560        let digest = Digest::from_bytes(Sha256::digest(bytes).into());
561        let mut decoder = VerifiedDecoder::new(Decoder::tar(), digest, bytes.len() as u64, digest);
562        let mut output = [0; 9];
563        let mut length = 0;
564        decoder
565            .push(bytes, |chunk| {
566                output[length..length + chunk.len()].copy_from_slice(chunk);
567                length += chunk.len();
568                Ok::<_, ()>(())
569            })
570            .unwrap();
571        decoder.finish(|_| Ok::<_, ()>(())).unwrap();
572        assert_eq!(&output, bytes);
573    }
574
575    #[test]
576    fn compressed_only_accepts_content_without_a_diff_id() {
577        let bytes = b"artifact contents";
578        let digest = Digest::from_bytes(Sha256::digest(bytes).into());
579        let mut decoder =
580            VerifiedDecoder::compressed_only(Decoder::tar(), digest, bytes.len() as u64);
581        decoder.push(bytes, |_| Ok::<_, ()>(())).unwrap();
582        decoder.finish(|_| Ok::<_, ()>(())).unwrap();
583        assert_eq!(decoder.decompressed_size(), bytes.len() as u64);
584    }
585
586    #[test]
587    fn compressed_only_still_verifies_descriptor_digest_and_size() {
588        let bytes = b"artifact contents";
589        let digest = Digest::from_bytes(Sha256::digest(bytes).into());
590        let mut wrong_digest = VerifiedDecoder::compressed_only(
591            Decoder::tar(),
592            Digest::from_bytes([9; 32]),
593            bytes.len() as u64,
594        );
595        wrong_digest.push(bytes, |_| Ok::<_, ()>(())).unwrap();
596        assert!(wrong_digest.finish(|_| Ok::<_, ()>(())).is_err());
597
598        let mut wrong_size = VerifiedDecoder::compressed_only(Decoder::tar(), digest, 1);
599        wrong_size.push(bytes, |_| Ok::<_, ()>(())).unwrap();
600        assert!(wrong_size.finish(|_| Ok::<_, ()>(())).is_err());
601    }
602
603    #[test]
604    fn verifies_and_extracts_one_entry() {
605        let mut tar = [0u8; 2048];
606        tar[..6].copy_from_slice(b"wanted");
607        write_octal(&mut tar[100..108], 0o644);
608        write_octal(&mut tar[124..136], 5);
609        tar[148..156].fill(b' ');
610        tar[156] = b'0';
611        let checksum = tar[..512].iter().map(|byte| u64::from(*byte)).sum();
612        write_octal(&mut tar[148..156], checksum);
613        tar[512..517].copy_from_slice(b"hello");
614
615        let digest = Digest::from_bytes(Sha256::digest(tar).into());
616        let decoder = VerifiedDecoder::new(Decoder::tar(), digest, tar.len() as u64, digest);
617        let mut extractor = VerifiedEntryExtractor::new(decoder, b"wanted");
618        let mut output = [0u8; 5];
619        let mut length = 0;
620        for fragment in tar.chunks(7) {
621            extractor
622                .push(fragment, |bytes| {
623                    output[length..length + bytes.len()].copy_from_slice(bytes);
624                    length += bytes.len();
625                    Ok::<_, ()>(())
626                })
627                .unwrap();
628        }
629        extractor.finish(|_| Ok::<_, ()>(())).unwrap();
630        assert_eq!(&output, b"hello");
631        assert_eq!(extractor.decompressed_size(), tar.len() as u64);
632        assert_eq!(extractor.extracted_size(), 5);
633    }
634
635    fn write_octal(field: &mut [u8], value: u64) {
636        field.fill(b'0');
637        let digits = field.len() - 1;
638        field[digits] = 0;
639        let mut value = value;
640        for byte in field[..digits].iter_mut().rev() {
641            *byte = b'0' + (value & 7) as u8;
642            value >>= 3;
643        }
644    }
645
646    #[test]
647    fn commits_only_after_archive_and_integrity_finish() {
648        let tar = [0; 1024];
649        let digest = Digest::from_bytes(Sha256::digest(tar).into());
650        let mut path = [0; 64];
651        let mut link = [0; 64];
652        let mut pax = [0; 64];
653        let archive = Archive::new(ArchiveBuffers {
654            path: &mut path,
655            link: &mut link,
656            pax: &mut pax,
657        });
658        let decoder = VerifiedDecoder::new(Decoder::tar(), digest, tar.len() as u64, digest);
659        let mut applier = LayerApplier::new(decoder, archive);
660        let mut sink = TransactionSink::default();
661        applier.push(&tar, &mut sink).unwrap();
662        assert!(sink.began);
663        assert!(!sink.committed);
664        applier.finish(&mut sink).unwrap();
665        assert!(sink.committed);
666        assert!(!sink.aborted);
667    }
668
669    #[test]
670    fn aborts_when_integrity_fails() {
671        let tar = [0; 1024];
672        let diff_id = Digest::from_bytes(Sha256::digest(tar).into());
673        let mut path = [0; 64];
674        let mut link = [0; 64];
675        let mut pax = [0; 64];
676        let archive = Archive::new(ArchiveBuffers {
677            path: &mut path,
678            link: &mut link,
679            pax: &mut pax,
680        });
681        let decoder = VerifiedDecoder::new(
682            Decoder::tar(),
683            Digest::from_bytes([1; 32]),
684            tar.len() as u64,
685            diff_id,
686        );
687        let mut applier = LayerApplier::new(decoder, archive);
688        let mut sink = TransactionSink::default();
689        applier.push(&tar, &mut sink).unwrap();
690        assert!(applier.finish(&mut sink).is_err());
691        assert!(sink.aborted);
692        assert!(!sink.committed);
693    }
694
695    #[cfg(feature = "gzip")]
696    #[test]
697    fn verifies_gzip_layers() {
698        const ENCODED: &[u8] = &[
699            0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9,
700            0xc9, 0x07, 0x00, 0x86, 0xa6, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00,
701        ];
702        let compressed = Digest::from_bytes(Sha256::digest(ENCODED).into());
703        let diff_id = Digest::from_bytes(Sha256::digest(b"hello").into());
704        let mut history = [0; gzip_zero::HISTORY_SIZE];
705        let decoder = Decoder::gzip(gzip_zero::DecoderBuffers {
706            history: &mut history,
707        })
708        .unwrap();
709        let mut decoder = VerifiedDecoder::new(decoder, compressed, ENCODED.len() as u64, diff_id);
710        let mut output = [0; 5];
711        let mut length = 0;
712        for byte in ENCODED {
713            decoder
714                .push(core::slice::from_ref(byte), |chunk| {
715                    output[length..length + chunk.len()].copy_from_slice(chunk);
716                    length += chunk.len();
717                    Ok::<_, ()>(())
718                })
719                .unwrap();
720        }
721        decoder.finish(|_| Ok::<_, ()>(())).unwrap();
722        assert_eq!(&output, b"hello");
723    }
724
725    #[cfg(feature = "zstd")]
726    #[test]
727    fn verifies_zstd_layers() {
728        const ENCODED: &[u8] = &[
729            0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x05, 0x29, 0, 0, b'h', b'e', b'l', b'l', b'o',
730        ];
731        let compressed = Digest::from_bytes(Sha256::digest(ENCODED).into());
732        let diff_id = Digest::from_bytes(Sha256::digest(b"hello").into());
733        let mut history = [0; 5];
734        let mut block = [0; 5];
735        let mut literals = [0; 5];
736        let decoder = Decoder::zstd(zstd_zero::DecoderBuffers {
737            history: &mut history,
738            block: &mut block,
739            literals: &mut literals,
740        });
741        let mut decoder = VerifiedDecoder::new(decoder, compressed, ENCODED.len() as u64, diff_id);
742        let mut output = [0; 5];
743        let mut length = 0;
744        decoder
745            .push(ENCODED, |chunk| {
746                output[length..length + chunk.len()].copy_from_slice(chunk);
747                length += chunk.len();
748                Ok::<_, ()>(())
749            })
750            .unwrap();
751        decoder.finish(|_| Ok::<_, ()>(())).unwrap();
752        assert_eq!(&output, b"hello");
753    }
754}