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)]
133struct Crc32 {
134    hasher: crc_fast::Digest,
135}
136
137impl Default for Crc32 {
138    fn default() -> Self {
139        Self {
140            hasher: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc32IsoHdlc),
141        }
142    }
143}
144
145impl Crc32 {
146    fn update(&mut self, bytes: &[u8]) {
147        self.hasher.update(bytes);
148    }
149
150    fn finalize(self) -> Bytes {
151        let checksum = self.hasher.finalize() as u32;
152
153        Bytes::copy_from_slice(checksum.to_be_bytes().as_slice())
154    }
155
156    // Size of the checksum in bytes
157    fn size() -> u64 {
158        4
159    }
160}
161
162impl Checksum for Crc32 {
163    fn update(&mut self, bytes: &[u8]) {
164        Self::update(self, bytes)
165    }
166    fn finalize(self: Box<Self>) -> Bytes {
167        Self::finalize(*self)
168    }
169    fn size(&self) -> u64 {
170        Self::size()
171    }
172}
173
174#[derive(Debug)]
175struct Crc32c {
176    hasher: crc_fast::Digest,
177}
178
179impl Default for Crc32c {
180    fn default() -> Self {
181        Self {
182            hasher: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc32Iscsi),
183        }
184    }
185}
186
187impl Crc32c {
188    fn update(&mut self, bytes: &[u8]) {
189        self.hasher.update(bytes);
190    }
191
192    fn finalize(self) -> Bytes {
193        let checksum = self.hasher.finalize() as u32;
194
195        Bytes::copy_from_slice(checksum.to_be_bytes().as_slice())
196    }
197
198    // Size of the checksum in bytes
199    fn size() -> u64 {
200        4
201    }
202}
203
204impl Checksum for Crc32c {
205    fn update(&mut self, bytes: &[u8]) {
206        Self::update(self, bytes)
207    }
208    fn finalize(self: Box<Self>) -> Bytes {
209        Self::finalize(*self)
210    }
211    fn size(&self) -> u64 {
212        Self::size()
213    }
214}
215
216#[derive(Debug)]
217struct Crc64Nvme {
218    hasher: crc_fast::Digest,
219}
220
221impl Default for Crc64Nvme {
222    fn default() -> Self {
223        Self {
224            hasher: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc64Nvme),
225        }
226    }
227}
228
229impl Crc64Nvme {
230    fn update(&mut self, bytes: &[u8]) {
231        self.hasher.update(bytes);
232    }
233
234    fn finalize(self) -> Bytes {
235        Bytes::copy_from_slice(self.hasher.finalize().to_be_bytes().as_slice())
236    }
237
238    // Size of the checksum in bytes
239    fn size() -> u64 {
240        8
241    }
242}
243
244impl Checksum for Crc64Nvme {
245    fn update(&mut self, bytes: &[u8]) {
246        Self::update(self, bytes)
247    }
248    fn finalize(self: Box<Self>) -> Bytes {
249        Self::finalize(*self)
250    }
251    fn size(&self) -> u64 {
252        Self::size()
253    }
254}
255
256#[derive(Debug, Default)]
257struct Sha1 {
258    hasher: sha1::Sha1,
259}
260
261impl Sha1 {
262    fn update(&mut self, bytes: &[u8]) {
263        use sha1::Digest;
264        self.hasher.update(bytes);
265    }
266
267    fn finalize(self) -> Bytes {
268        use sha1::Digest;
269        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
270    }
271
272    // Size of the checksum in bytes
273    fn size() -> u64 {
274        use sha1::Digest;
275        sha1::Sha1::output_size() as u64
276    }
277}
278
279impl Checksum for Sha1 {
280    fn update(&mut self, bytes: &[u8]) {
281        Self::update(self, bytes)
282    }
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#[derive(Debug, Default)]
293struct Sha256 {
294    hasher: sha2::Sha256,
295}
296
297impl Sha256 {
298    fn update(&mut self, bytes: &[u8]) {
299        use sha2::Digest;
300        self.hasher.update(bytes);
301    }
302
303    fn finalize(self) -> Bytes {
304        use sha2::Digest;
305        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
306    }
307
308    // Size of the checksum in bytes
309    fn size() -> u64 {
310        use sha2::Digest;
311        sha2::Sha256::output_size() as u64
312    }
313}
314
315impl Checksum for Sha256 {
316    fn update(&mut self, bytes: &[u8]) {
317        Self::update(self, bytes);
318    }
319    fn finalize(self: Box<Self>) -> Bytes {
320        Self::finalize(*self)
321    }
322    fn size(&self) -> u64 {
323        Self::size()
324    }
325}
326
327#[allow(dead_code)]
328#[derive(Debug, Default)]
329struct Md5 {
330    hasher: md5::Md5,
331}
332
333impl Md5 {
334    #[warn(dead_code)]
335    fn update(&mut self, bytes: &[u8]) {
336        use md5::Digest;
337        self.hasher.update(bytes);
338    }
339
340    #[warn(dead_code)]
341    fn finalize(self) -> Bytes {
342        use md5::Digest;
343        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
344    }
345
346    // Size of the checksum in bytes
347    #[warn(dead_code)]
348    fn size() -> u64 {
349        use md5::Digest;
350        md5::Md5::output_size() as u64
351    }
352}
353
354impl Checksum for Md5 {
355    fn update(&mut self, bytes: &[u8]) {
356        Self::update(self, bytes)
357    }
358    fn finalize(self: Box<Self>) -> Bytes {
359        Self::finalize(*self)
360    }
361    fn size(&self) -> u64 {
362        Self::size()
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::{
369        http::{
370            CRC_32_C_HEADER_NAME, CRC_32_HEADER_NAME, MD5_HEADER_NAME, SHA_1_HEADER_NAME,
371            SHA_256_HEADER_NAME,
372        },
373        Crc32, Crc32c, Md5, Sha1, Sha256,
374    };
375
376    use crate::http::HttpChecksum;
377    use crate::ChecksumAlgorithm;
378
379    use aws_smithy_types::base64;
380    use http::HeaderValue;
381    use pretty_assertions::assert_eq;
382    use std::fmt::Write;
383
384    const TEST_DATA: &str = r#"test data"#;
385
386    fn base64_encoded_checksum_to_hex_string(header_value: &HeaderValue) -> String {
387        let decoded_checksum = base64::decode(header_value.to_str().unwrap()).unwrap();
388        let decoded_checksum = decoded_checksum
389            .into_iter()
390            .fold(String::new(), |mut acc, byte| {
391                write!(acc, "{byte:02X?}").expect("string will always be writeable");
392                acc
393            });
394
395        format!("0x{}", decoded_checksum)
396    }
397
398    #[test]
399    fn test_crc32_checksum() {
400        let mut checksum = Crc32::default();
401        checksum.update(TEST_DATA.as_bytes());
402        let checksum_result = Box::new(checksum).headers();
403        let encoded_checksum = checksum_result.get(CRC_32_HEADER_NAME).unwrap();
404        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
405
406        let expected_checksum = "0xD308AEB2";
407
408        assert_eq!(decoded_checksum, expected_checksum);
409    }
410
411    // TODO(https://github.com/zowens/crc32c/issues/34)
412    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
413    #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
414    #[test]
415    fn test_crc32c_checksum() {
416        let mut checksum = Crc32c::default();
417        checksum.update(TEST_DATA.as_bytes());
418        let checksum_result = Box::new(checksum).headers();
419        let encoded_checksum = checksum_result.get(CRC_32_C_HEADER_NAME).unwrap();
420        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
421
422        let expected_checksum = "0x3379B4CA";
423
424        assert_eq!(decoded_checksum, expected_checksum);
425    }
426
427    #[test]
428    fn test_crc64nvme_checksum() {
429        use crate::{http::CRC_64_NVME_HEADER_NAME, Crc64Nvme};
430        let mut checksum = Crc64Nvme::default();
431        checksum.update(TEST_DATA.as_bytes());
432        let checksum_result = Box::new(checksum).headers();
433        let encoded_checksum = checksum_result.get(CRC_64_NVME_HEADER_NAME).unwrap();
434        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
435
436        let expected_checksum = "0xAECAF3AF9C98A855";
437
438        assert_eq!(decoded_checksum, expected_checksum);
439    }
440
441    #[test]
442    fn test_sha1_checksum() {
443        let mut checksum = Sha1::default();
444        checksum.update(TEST_DATA.as_bytes());
445        let checksum_result = Box::new(checksum).headers();
446        let encoded_checksum = checksum_result.get(SHA_1_HEADER_NAME).unwrap();
447        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
448
449        let expected_checksum = "0xF48DD853820860816C75D54D0F584DC863327A7C";
450
451        assert_eq!(decoded_checksum, expected_checksum);
452    }
453
454    #[test]
455    fn test_sha256_checksum() {
456        let mut checksum = Sha256::default();
457        checksum.update(TEST_DATA.as_bytes());
458        let checksum_result = Box::new(checksum).headers();
459        let encoded_checksum = checksum_result.get(SHA_256_HEADER_NAME).unwrap();
460        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
461
462        let expected_checksum =
463            "0x916F0027A575074CE72A331777C3478D6513F786A591BD892DA1A577BF2335F9";
464
465        assert_eq!(decoded_checksum, expected_checksum);
466    }
467
468    #[test]
469    fn test_md5_checksum() {
470        let mut checksum = Md5::default();
471        checksum.update(TEST_DATA.as_bytes());
472        let checksum_result = Box::new(checksum).headers();
473        let encoded_checksum = checksum_result.get(MD5_HEADER_NAME).unwrap();
474        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
475
476        let expected_checksum = "0xEB733A00C0C9D336E65691A37AB54293";
477
478        assert_eq!(decoded_checksum, expected_checksum);
479    }
480
481    #[test]
482    fn test_checksum_algorithm_returns_error_for_unknown() {
483        let error = "some invalid checksum algorithm"
484            .parse::<ChecksumAlgorithm>()
485            .expect_err("it should error");
486        assert_eq!(
487            "some invalid checksum algorithm",
488            error.checksum_algorithm()
489        );
490    }
491}