1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8#![allow(clippy::derive_partial_eq_without_eq)]
10#![warn(
11 rustdoc::missing_crate_level_docs,
13 unreachable_pub,
14 rust_2018_idioms
15)]
16
17use crate::error::UnknownChecksumAlgorithmError;
20
21use bytes::Bytes;
22use std::{fmt::Debug, str::FromStr};
23
24pub mod body;
25pub mod error;
26pub mod http;
27
28pub 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#[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 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 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 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 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
109pub trait Checksum: Send + Sync {
115 fn update(&mut self, bytes: &[u8]);
117 fn finalize(self: Box<Self>) -> Bytes;
125 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 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 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
204impl 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 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 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 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 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 #[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}