Skip to main content

s4_codec/
passthrough.rs

1//! 無圧縮 codec — テストおよび圧縮無効化フラグ用。
2//!
3//! crc32c は実 (SSE 4.2 / ARM CRC instruction を `crc32c` crate が利用) を入れているので
4//! manifest の改ざん / S3 上の bit rot は確実に検出できる。
5
6use bytes::Bytes;
7
8use crate::{ChunkManifest, Codec, CodecError, CodecKind};
9
10pub struct Passthrough;
11
12#[async_trait::async_trait]
13impl Codec for Passthrough {
14    fn kind(&self) -> CodecKind {
15        CodecKind::Passthrough
16    }
17
18    async fn compress(&self, input: Bytes) -> Result<(Bytes, ChunkManifest), CodecError> {
19        let len = input.len() as u64;
20        let crc = crc32c::crc32c(&input);
21        Ok((
22            input,
23            ChunkManifest {
24                codec: CodecKind::Passthrough,
25                original_size: len,
26                compressed_size: len,
27                crc32c: crc,
28            },
29        ))
30    }
31
32    async fn decompress(
33        &self,
34        input: Bytes,
35        manifest: &ChunkManifest,
36    ) -> Result<Bytes, CodecError> {
37        if manifest.codec != CodecKind::Passthrough {
38            return Err(CodecError::CodecMismatch {
39                expected: CodecKind::Passthrough,
40                got: manifest.codec,
41            });
42        }
43        let crc = crc32c::crc32c(&input);
44        if crc != manifest.crc32c {
45            return Err(CodecError::CrcMismatch {
46                expected: manifest.crc32c,
47                got: crc,
48            });
49        }
50        if input.len() as u64 != manifest.compressed_size {
51            return Err(CodecError::SizeMismatch {
52                expected: manifest.compressed_size,
53                got: input.len() as u64,
54            });
55        }
56        Ok(input)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[tokio::test]
65    async fn roundtrip_preserves_bytes() {
66        let codec = Passthrough;
67        let input = Bytes::from_static(b"hello squished s3");
68        let (compressed, manifest) = codec.compress(input.clone()).await.unwrap();
69        assert_eq!(compressed, input);
70        assert_eq!(manifest.codec, CodecKind::Passthrough);
71        assert_eq!(manifest.original_size, input.len() as u64);
72        assert_eq!(manifest.compressed_size, input.len() as u64);
73        assert_ne!(manifest.crc32c, 0, "crc32c must not be the stub zero");
74        let decompressed = codec.decompress(compressed, &manifest).await.unwrap();
75        assert_eq!(decompressed, input);
76    }
77
78    #[tokio::test]
79    async fn detects_corrupted_payload() {
80        let codec = Passthrough;
81        let original = Bytes::from_static(b"hello squished s3");
82        let (_, manifest) = codec.compress(original.clone()).await.unwrap();
83        let corrupted = Bytes::from_static(b"hello SQUISHED s3");
84        let err = codec.decompress(corrupted, &manifest).await.unwrap_err();
85        assert!(matches!(err, CodecError::CrcMismatch { .. }));
86    }
87
88    #[tokio::test]
89    async fn rejects_codec_mismatch() {
90        let codec = Passthrough;
91        let original = Bytes::from_static(b"hello");
92        let (_, mut manifest) = codec.compress(original.clone()).await.unwrap();
93        manifest.codec = CodecKind::CpuZstd;
94        let err = codec.decompress(original, &manifest).await.unwrap_err();
95        assert!(matches!(err, CodecError::CodecMismatch { .. }));
96    }
97}