Skip to main content

oci_zero/
layer.rs

1//! Verified, allocation-free OCI layer decoding.
2
3use 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// Keeping codec state inline is intentional: indirection would require an
45// allocator or a second caller-owned object with a more fragile lifetime API.
46#[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
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, 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
239/// Transactionally decodes, verifies, parses, and applies one OCI layer.
240pub 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    /// Creates a decoder that verifies the compressed descriptor only.
358    ///
359    /// OCI artifacts commonly use layer archives without an image config's
360    /// `rootfs.diff_ids`. Decoded byte accounting remains available, but no
361    /// uncompressed digest is expected or checked.
362    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        // A 1 KiB window is moved one byte by the first block. The second block
836        // then wraps, leaving its final byte buffered until finish().
837        let encoded = [
838            0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x00, // frame, 1 KiB window
839            0x0a, 0x00, 0x00, b'a', // non-final RLE block, size 1
840            0x03, 0x20, 0x00, b'b', // final RLE block, size 1024
841        ];
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}