aws_smithy_checksums/
lib.rs1#![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)]
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 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 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 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 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 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 #[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 #[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}