aws_smithy_checksums/
lib.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/* Automatically managed default lints */
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8/* End of automatically managed default lints */
9#![allow(clippy::derive_partial_eq_without_eq)]
10#![warn(
11    // missing_docs,
12    rustdoc::missing_crate_level_docs,
13    unreachable_pub,
14    rust_2018_idioms
15)]
16
17//! Checksum calculation and verification callbacks.
18
19use crate::error::UnknownChecksumAlgorithmError;
20use bytes::Bytes;
21use std::str::FromStr;
22
23pub mod body;
24pub mod error;
25pub mod http;
26
27// Valid checksum algorithm names
28pub const CRC_32_NAME: &str = "crc32";
29pub const CRC_32_C_NAME: &str = "crc32c";
30pub const SHA_1_NAME: &str = "sha1";
31pub const SHA_256_NAME: &str = "sha256";
32pub const MD5_NAME: &str = "md5";
33
34/// We only support checksum calculation and validation for these checksum algorithms.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum ChecksumAlgorithm {
37    Crc32,
38    Crc32c,
39    Md5,
40    Sha1,
41    Sha256,
42}
43
44impl FromStr for ChecksumAlgorithm {
45    type Err = UnknownChecksumAlgorithmError;
46
47    /// Create a new `ChecksumAlgorithm` from an algorithm name. Valid algorithm names are:
48    /// - "crc32"
49    /// - "crc32c"
50    /// - "sha1"
51    /// - "sha256"
52    /// - "md5"
53    ///
54    /// Passing an invalid name will return an error.
55    fn from_str(checksum_algorithm: &str) -> Result<Self, Self::Err> {
56        if checksum_algorithm.eq_ignore_ascii_case(CRC_32_NAME) {
57            Ok(Self::Crc32)
58        } else if checksum_algorithm.eq_ignore_ascii_case(CRC_32_C_NAME) {
59            Ok(Self::Crc32c)
60        } else if checksum_algorithm.eq_ignore_ascii_case(SHA_1_NAME) {
61            Ok(Self::Sha1)
62        } else if checksum_algorithm.eq_ignore_ascii_case(SHA_256_NAME) {
63            Ok(Self::Sha256)
64        } else if checksum_algorithm.eq_ignore_ascii_case(MD5_NAME) {
65            Ok(Self::Md5)
66        } else {
67            Err(UnknownChecksumAlgorithmError::new(checksum_algorithm))
68        }
69    }
70}
71
72impl ChecksumAlgorithm {
73    /// Return the `HttpChecksum` implementor for this algorithm
74    pub fn into_impl(self) -> Box<dyn http::HttpChecksum> {
75        match self {
76            Self::Crc32 => Box::<Crc32>::default(),
77            Self::Crc32c => Box::<Crc32c>::default(),
78            Self::Md5 => Box::<Md5>::default(),
79            Self::Sha1 => Box::<Sha1>::default(),
80            Self::Sha256 => Box::<Sha256>::default(),
81        }
82    }
83
84    /// Return the name of this algorithm in string form
85    pub fn as_str(&self) -> &'static str {
86        match self {
87            Self::Crc32 => CRC_32_NAME,
88            Self::Crc32c => CRC_32_C_NAME,
89            Self::Md5 => MD5_NAME,
90            Self::Sha1 => SHA_1_NAME,
91            Self::Sha256 => SHA_256_NAME,
92        }
93    }
94}
95
96/// Types implementing this trait can calculate checksums.
97///
98/// Checksum algorithms are used to validate the integrity of data. Structs that implement this trait
99/// can be used as checksum calculators. This trait requires Send + Sync because these checksums are
100/// often used in a threaded context.
101pub trait Checksum: Send + Sync {
102    /// Given a slice of bytes, update this checksum's internal state.
103    fn update(&mut self, bytes: &[u8]);
104    /// "Finalize" this checksum, returning the calculated value as `Bytes` or an error that
105    /// occurred during checksum calculation.
106    ///
107    /// _HINT: To print this value in a human-readable hexadecimal format, you can use Rust's
108    /// builtin [formatter]._
109    ///
110    /// [formatter]: https://doc.rust-lang.org/std/fmt/trait.UpperHex.html
111    fn finalize(self: Box<Self>) -> Bytes;
112    /// Return the size of this checksum algorithms resulting checksum, in bytes.
113    ///
114    /// For example, the CRC32 checksum algorithm calculates a 32 bit checksum, so a CRC32 checksum
115    /// struct implementing this trait method would return `4`.
116    fn size(&self) -> u64;
117}
118
119#[derive(Debug, Default)]
120struct Crc32 {
121    hasher: crc32fast::Hasher,
122}
123
124impl Crc32 {
125    fn update(&mut self, bytes: &[u8]) {
126        self.hasher.update(bytes);
127    }
128
129    fn finalize(self) -> Bytes {
130        Bytes::copy_from_slice(self.hasher.finalize().to_be_bytes().as_slice())
131    }
132
133    // Size of the checksum in bytes
134    fn size() -> u64 {
135        4
136    }
137}
138
139impl Checksum for Crc32 {
140    fn update(&mut self, bytes: &[u8]) {
141        Self::update(self, bytes)
142    }
143    fn finalize(self: Box<Self>) -> Bytes {
144        Self::finalize(*self)
145    }
146    fn size(&self) -> u64 {
147        Self::size()
148    }
149}
150
151#[derive(Debug, Default)]
152struct Crc32c {
153    state: Option<u32>,
154}
155
156impl Crc32c {
157    fn update(&mut self, bytes: &[u8]) {
158        self.state = match self.state {
159            Some(crc) => Some(crc32c::crc32c_append(crc, bytes)),
160            None => Some(crc32c::crc32c(bytes)),
161        };
162    }
163
164    fn finalize(self) -> Bytes {
165        Bytes::copy_from_slice(self.state.unwrap_or_default().to_be_bytes().as_slice())
166    }
167
168    // Size of the checksum in bytes
169    fn size() -> u64 {
170        4
171    }
172}
173
174impl Checksum for Crc32c {
175    fn update(&mut self, bytes: &[u8]) {
176        Self::update(self, bytes)
177    }
178    fn finalize(self: Box<Self>) -> Bytes {
179        Self::finalize(*self)
180    }
181    fn size(&self) -> u64 {
182        Self::size()
183    }
184}
185
186#[derive(Debug, Default)]
187struct Sha1 {
188    hasher: sha1::Sha1,
189}
190
191impl Sha1 {
192    fn update(&mut self, bytes: &[u8]) {
193        use sha1::Digest;
194        self.hasher.update(bytes);
195    }
196
197    fn finalize(self) -> Bytes {
198        use sha1::Digest;
199        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
200    }
201
202    // Size of the checksum in bytes
203    fn size() -> u64 {
204        use sha1::Digest;
205        sha1::Sha1::output_size() as u64
206    }
207}
208
209impl Checksum for Sha1 {
210    fn update(&mut self, bytes: &[u8]) {
211        Self::update(self, bytes)
212    }
213
214    fn finalize(self: Box<Self>) -> Bytes {
215        Self::finalize(*self)
216    }
217    fn size(&self) -> u64 {
218        Self::size()
219    }
220}
221
222#[derive(Debug, Default)]
223struct Sha256 {
224    hasher: sha2::Sha256,
225}
226
227impl Sha256 {
228    fn update(&mut self, bytes: &[u8]) {
229        use sha2::Digest;
230        self.hasher.update(bytes);
231    }
232
233    fn finalize(self) -> Bytes {
234        use sha2::Digest;
235        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
236    }
237
238    // Size of the checksum in bytes
239    fn size() -> u64 {
240        use sha2::Digest;
241        sha2::Sha256::output_size() as u64
242    }
243}
244
245impl Checksum for Sha256 {
246    fn update(&mut self, bytes: &[u8]) {
247        Self::update(self, bytes);
248    }
249    fn finalize(self: Box<Self>) -> Bytes {
250        Self::finalize(*self)
251    }
252    fn size(&self) -> u64 {
253        Self::size()
254    }
255}
256
257#[derive(Debug, Default)]
258struct Md5 {
259    hasher: md5::Md5,
260}
261
262impl Md5 {
263    fn update(&mut self, bytes: &[u8]) {
264        use md5::Digest;
265        self.hasher.update(bytes);
266    }
267
268    fn finalize(self) -> Bytes {
269        use md5::Digest;
270        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
271    }
272
273    // Size of the checksum in bytes
274    fn size() -> u64 {
275        use md5::Digest;
276        md5::Md5::output_size() as u64
277    }
278}
279
280impl Checksum for Md5 {
281    fn update(&mut self, bytes: &[u8]) {
282        Self::update(self, bytes)
283    }
284    fn finalize(self: Box<Self>) -> Bytes {
285        Self::finalize(*self)
286    }
287    fn size(&self) -> u64 {
288        Self::size()
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::{
295        http::{
296            CRC_32_C_HEADER_NAME, CRC_32_HEADER_NAME, MD5_HEADER_NAME, SHA_1_HEADER_NAME,
297            SHA_256_HEADER_NAME,
298        },
299        Crc32, Crc32c, Md5, Sha1, Sha256,
300    };
301
302    use crate::http::HttpChecksum;
303    use crate::ChecksumAlgorithm;
304    use aws_smithy_types::base64;
305    use http::HeaderValue;
306    use pretty_assertions::assert_eq;
307    use std::fmt::Write;
308
309    const TEST_DATA: &str = r#"test data"#;
310
311    fn base64_encoded_checksum_to_hex_string(header_value: &HeaderValue) -> String {
312        let decoded_checksum = base64::decode(header_value.to_str().unwrap()).unwrap();
313        let decoded_checksum = decoded_checksum
314            .into_iter()
315            .fold(String::new(), |mut acc, byte| {
316                write!(acc, "{byte:02X?}").expect("string will always be writeable");
317                acc
318            });
319
320        format!("0x{}", decoded_checksum)
321    }
322
323    #[test]
324    fn test_crc32_checksum() {
325        let mut checksum = Crc32::default();
326        checksum.update(TEST_DATA.as_bytes());
327        let checksum_result = Box::new(checksum).headers();
328        let encoded_checksum = checksum_result.get(CRC_32_HEADER_NAME).unwrap();
329        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
330
331        let expected_checksum = "0xD308AEB2";
332
333        assert_eq!(decoded_checksum, expected_checksum);
334    }
335
336    // TODO(https://github.com/zowens/crc32c/issues/34)
337    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
338    #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
339    #[test]
340    fn test_crc32c_checksum() {
341        let mut checksum = Crc32c::default();
342        checksum.update(TEST_DATA.as_bytes());
343        let checksum_result = Box::new(checksum).headers();
344        let encoded_checksum = checksum_result.get(CRC_32_C_HEADER_NAME).unwrap();
345        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
346
347        let expected_checksum = "0x3379B4CA";
348
349        assert_eq!(decoded_checksum, expected_checksum);
350    }
351
352    #[test]
353    fn test_sha1_checksum() {
354        let mut checksum = Sha1::default();
355        checksum.update(TEST_DATA.as_bytes());
356        let checksum_result = Box::new(checksum).headers();
357        let encoded_checksum = checksum_result.get(SHA_1_HEADER_NAME).unwrap();
358        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
359
360        let expected_checksum = "0xF48DD853820860816C75D54D0F584DC863327A7C";
361
362        assert_eq!(decoded_checksum, expected_checksum);
363    }
364
365    #[test]
366    fn test_sha256_checksum() {
367        let mut checksum = Sha256::default();
368        checksum.update(TEST_DATA.as_bytes());
369        let checksum_result = Box::new(checksum).headers();
370        let encoded_checksum = checksum_result.get(SHA_256_HEADER_NAME).unwrap();
371        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
372
373        let expected_checksum =
374            "0x916F0027A575074CE72A331777C3478D6513F786A591BD892DA1A577BF2335F9";
375
376        assert_eq!(decoded_checksum, expected_checksum);
377    }
378
379    #[test]
380    fn test_md5_checksum() {
381        let mut checksum = Md5::default();
382        checksum.update(TEST_DATA.as_bytes());
383        let checksum_result = Box::new(checksum).headers();
384        let encoded_checksum = checksum_result.get(MD5_HEADER_NAME).unwrap();
385        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
386
387        let expected_checksum = "0xEB733A00C0C9D336E65691A37AB54293";
388
389        assert_eq!(decoded_checksum, expected_checksum);
390    }
391
392    #[test]
393    fn test_checksum_algorithm_returns_error_for_unknown() {
394        let error = "some invalid checksum algorithm"
395            .parse::<ChecksumAlgorithm>()
396            .expect_err("it should error");
397        assert_eq!(
398            "some invalid checksum algorithm",
399            error.checksum_algorithm()
400        );
401    }
402}