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#[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 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 #[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}