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