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