1use 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#[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
161pub 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
248pub 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 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 let encoded = [
872 0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x00, 0x0a, 0x00, 0x00, b'a', 0x03, 0x20, 0x00, b'b', ];
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}