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#[derive(Debug, Default)]
328struct Md5 {
329    hasher: md5::Md5,
330}
331
332impl Md5 {
333    fn update(&mut self, bytes: &[u8]) {
334        use md5::Digest;
335        self.hasher.update(bytes);
336    }
337
338    fn finalize(self) -> Bytes {
339        use md5::Digest;
340        Bytes::copy_from_slice(self.hasher.finalize().as_slice())
341    }
342
343    // Size of the checksum in bytes
344    fn size() -> u64 {
345        use md5::Digest;
346        md5::Md5::output_size() as u64
347    }
348}
349
350impl Checksum for Md5 {
351    fn update(&mut self, bytes: &[u8]) {
352        Self::update(self, bytes)
353    }
354    fn finalize(self: Box<Self>) -> Bytes {
355        Self::finalize(*self)
356    }
357    fn size(&self) -> u64 {
358        Self::size()
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::{
365        http::{
366            CRC_32_C_HEADER_NAME, CRC_32_HEADER_NAME, MD5_HEADER_NAME, SHA_1_HEADER_NAME,
367            SHA_256_HEADER_NAME,
368        },
369        Crc32, Crc32c, Md5, Sha1, Sha256,
370    };
371
372    use crate::http::HttpChecksum;
373    use crate::ChecksumAlgorithm;
374
375    use aws_smithy_types::base64;
376    use http::HeaderValue;
377    use pretty_assertions::assert_eq;
378    use std::fmt::Write;
379
380    const TEST_DATA: &str = r#"test data"#;
381
382    fn base64_encoded_checksum_to_hex_string(header_value: &HeaderValue) -> String {
383        let decoded_checksum = base64::decode(header_value.to_str().unwrap()).unwrap();
384        let decoded_checksum = decoded_checksum
385            .into_iter()
386            .fold(String::new(), |mut acc, byte| {
387                write!(acc, "{byte:02X?}").expect("string will always be writeable");
388                acc
389            });
390
391        format!("0x{}", decoded_checksum)
392    }
393
394    #[test]
395    fn test_crc32_checksum() {
396        let mut checksum = Crc32::default();
397        checksum.update(TEST_DATA.as_bytes());
398        let checksum_result = Box::new(checksum).headers();
399        let encoded_checksum = checksum_result.get(CRC_32_HEADER_NAME).unwrap();
400        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
401
402        let expected_checksum = "0xD308AEB2";
403
404        assert_eq!(decoded_checksum, expected_checksum);
405    }
406
407    // TODO(https://github.com/zowens/crc32c/issues/34)
408    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
409    #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
410    #[test]
411    fn test_crc32c_checksum() {
412        let mut checksum = Crc32c::default();
413        checksum.update(TEST_DATA.as_bytes());
414        let checksum_result = Box::new(checksum).headers();
415        let encoded_checksum = checksum_result.get(CRC_32_C_HEADER_NAME).unwrap();
416        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
417
418        let expected_checksum = "0x3379B4CA";
419
420        assert_eq!(decoded_checksum, expected_checksum);
421    }
422
423    #[test]
424    fn test_crc64nvme_checksum() {
425        use crate::{http::CRC_64_NVME_HEADER_NAME, Crc64Nvme};
426        let mut checksum = Crc64Nvme::default();
427        checksum.update(TEST_DATA.as_bytes());
428        let checksum_result = Box::new(checksum).headers();
429        let encoded_checksum = checksum_result.get(CRC_64_NVME_HEADER_NAME).unwrap();
430        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
431
432        let expected_checksum = "0xAECAF3AF9C98A855";
433
434        assert_eq!(decoded_checksum, expected_checksum);
435    }
436
437    #[test]
438    fn test_sha1_checksum() {
439        let mut checksum = Sha1::default();
440        checksum.update(TEST_DATA.as_bytes());
441        let checksum_result = Box::new(checksum).headers();
442        let encoded_checksum = checksum_result.get(SHA_1_HEADER_NAME).unwrap();
443        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
444
445        let expected_checksum = "0xF48DD853820860816C75D54D0F584DC863327A7C";
446
447        assert_eq!(decoded_checksum, expected_checksum);
448    }
449
450    #[test]
451    fn test_sha256_checksum() {
452        let mut checksum = Sha256::default();
453        checksum.update(TEST_DATA.as_bytes());
454        let checksum_result = Box::new(checksum).headers();
455        let encoded_checksum = checksum_result.get(SHA_256_HEADER_NAME).unwrap();
456        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
457
458        let expected_checksum =
459            "0x916F0027A575074CE72A331777C3478D6513F786A591BD892DA1A577BF2335F9";
460
461        assert_eq!(decoded_checksum, expected_checksum);
462    }
463
464    #[test]
465    fn test_md5_checksum() {
466        let mut checksum = Md5::default();
467        checksum.update(TEST_DATA.as_bytes());
468        let checksum_result = Box::new(checksum).headers();
469        let encoded_checksum = checksum_result.get(MD5_HEADER_NAME).unwrap();
470        let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
471
472        let expected_checksum = "0xEB733A00C0C9D336E65691A37AB54293";
473
474        assert_eq!(decoded_checksum, expected_checksum);
475    }
476
477    #[test]
478    fn test_checksum_algorithm_returns_error_for_unknown() {
479        let error = "some invalid checksum algorithm"
480            .parse::<ChecksumAlgorithm>()
481            .expect_err("it should error");
482        assert_eq!(
483            "some invalid checksum algorithm",
484            error.checksum_algorithm()
485        );
486    }
487}