aws_smithy_checksums/
lib.rs
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;
20use bytes::Bytes;
21use std::str::FromStr;
22
23pub mod body;
24pub mod error;
25pub mod http;
26
27pub const CRC_32_NAME: &str = "crc32";
29pub const CRC_32_C_NAME: &str = "crc32c";
30pub const SHA_1_NAME: &str = "sha1";
31pub const SHA_256_NAME: &str = "sha256";
32pub const MD5_NAME: &str = "md5";
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum ChecksumAlgorithm {
37 Crc32,
38 Crc32c,
39 Md5,
40 Sha1,
41 Sha256,
42}
43
44impl FromStr for ChecksumAlgorithm {
45 type Err = UnknownChecksumAlgorithmError;
46
47 fn from_str(checksum_algorithm: &str) -> Result<Self, Self::Err> {
56 if checksum_algorithm.eq_ignore_ascii_case(CRC_32_NAME) {
57 Ok(Self::Crc32)
58 } else if checksum_algorithm.eq_ignore_ascii_case(CRC_32_C_NAME) {
59 Ok(Self::Crc32c)
60 } else if checksum_algorithm.eq_ignore_ascii_case(SHA_1_NAME) {
61 Ok(Self::Sha1)
62 } else if checksum_algorithm.eq_ignore_ascii_case(SHA_256_NAME) {
63 Ok(Self::Sha256)
64 } else if checksum_algorithm.eq_ignore_ascii_case(MD5_NAME) {
65 Ok(Self::Md5)
66 } else {
67 Err(UnknownChecksumAlgorithmError::new(checksum_algorithm))
68 }
69 }
70}
71
72impl ChecksumAlgorithm {
73 pub fn into_impl(self) -> Box<dyn http::HttpChecksum> {
75 match self {
76 Self::Crc32 => Box::<Crc32>::default(),
77 Self::Crc32c => Box::<Crc32c>::default(),
78 Self::Md5 => Box::<Md5>::default(),
79 Self::Sha1 => Box::<Sha1>::default(),
80 Self::Sha256 => Box::<Sha256>::default(),
81 }
82 }
83
84 pub fn as_str(&self) -> &'static str {
86 match self {
87 Self::Crc32 => CRC_32_NAME,
88 Self::Crc32c => CRC_32_C_NAME,
89 Self::Md5 => MD5_NAME,
90 Self::Sha1 => SHA_1_NAME,
91 Self::Sha256 => SHA_256_NAME,
92 }
93 }
94}
95
96pub trait Checksum: Send + Sync {
102 fn update(&mut self, bytes: &[u8]);
104 fn finalize(self: Box<Self>) -> Bytes;
112 fn size(&self) -> u64;
117}
118
119#[derive(Debug, Default)]
120struct Crc32 {
121 hasher: crc32fast::Hasher,
122}
123
124impl Crc32 {
125 fn update(&mut self, bytes: &[u8]) {
126 self.hasher.update(bytes);
127 }
128
129 fn finalize(self) -> Bytes {
130 Bytes::copy_from_slice(self.hasher.finalize().to_be_bytes().as_slice())
131 }
132
133 fn size() -> u64 {
135 4
136 }
137}
138
139impl Checksum for Crc32 {
140 fn update(&mut self, bytes: &[u8]) {
141 Self::update(self, bytes)
142 }
143 fn finalize(self: Box<Self>) -> Bytes {
144 Self::finalize(*self)
145 }
146 fn size(&self) -> u64 {
147 Self::size()
148 }
149}
150
151#[derive(Debug, Default)]
152struct Crc32c {
153 state: Option<u32>,
154}
155
156impl Crc32c {
157 fn update(&mut self, bytes: &[u8]) {
158 self.state = match self.state {
159 Some(crc) => Some(crc32c::crc32c_append(crc, bytes)),
160 None => Some(crc32c::crc32c(bytes)),
161 };
162 }
163
164 fn finalize(self) -> Bytes {
165 Bytes::copy_from_slice(self.state.unwrap_or_default().to_be_bytes().as_slice())
166 }
167
168 fn size() -> u64 {
170 4
171 }
172}
173
174impl Checksum for Crc32c {
175 fn update(&mut self, bytes: &[u8]) {
176 Self::update(self, bytes)
177 }
178 fn finalize(self: Box<Self>) -> Bytes {
179 Self::finalize(*self)
180 }
181 fn size(&self) -> u64 {
182 Self::size()
183 }
184}
185
186#[derive(Debug, Default)]
187struct Sha1 {
188 hasher: sha1::Sha1,
189}
190
191impl Sha1 {
192 fn update(&mut self, bytes: &[u8]) {
193 use sha1::Digest;
194 self.hasher.update(bytes);
195 }
196
197 fn finalize(self) -> Bytes {
198 use sha1::Digest;
199 Bytes::copy_from_slice(self.hasher.finalize().as_slice())
200 }
201
202 fn size() -> u64 {
204 use sha1::Digest;
205 sha1::Sha1::output_size() as u64
206 }
207}
208
209impl Checksum for Sha1 {
210 fn update(&mut self, bytes: &[u8]) {
211 Self::update(self, bytes)
212 }
213
214 fn finalize(self: Box<Self>) -> Bytes {
215 Self::finalize(*self)
216 }
217 fn size(&self) -> u64 {
218 Self::size()
219 }
220}
221
222#[derive(Debug, Default)]
223struct Sha256 {
224 hasher: sha2::Sha256,
225}
226
227impl Sha256 {
228 fn update(&mut self, bytes: &[u8]) {
229 use sha2::Digest;
230 self.hasher.update(bytes);
231 }
232
233 fn finalize(self) -> Bytes {
234 use sha2::Digest;
235 Bytes::copy_from_slice(self.hasher.finalize().as_slice())
236 }
237
238 fn size() -> u64 {
240 use sha2::Digest;
241 sha2::Sha256::output_size() as u64
242 }
243}
244
245impl Checksum for Sha256 {
246 fn update(&mut self, bytes: &[u8]) {
247 Self::update(self, bytes);
248 }
249 fn finalize(self: Box<Self>) -> Bytes {
250 Self::finalize(*self)
251 }
252 fn size(&self) -> u64 {
253 Self::size()
254 }
255}
256
257#[derive(Debug, Default)]
258struct Md5 {
259 hasher: md5::Md5,
260}
261
262impl Md5 {
263 fn update(&mut self, bytes: &[u8]) {
264 use md5::Digest;
265 self.hasher.update(bytes);
266 }
267
268 fn finalize(self) -> Bytes {
269 use md5::Digest;
270 Bytes::copy_from_slice(self.hasher.finalize().as_slice())
271 }
272
273 fn size() -> u64 {
275 use md5::Digest;
276 md5::Md5::output_size() as u64
277 }
278}
279
280impl Checksum for Md5 {
281 fn update(&mut self, bytes: &[u8]) {
282 Self::update(self, bytes)
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#[cfg(test)]
293mod tests {
294 use super::{
295 http::{
296 CRC_32_C_HEADER_NAME, CRC_32_HEADER_NAME, MD5_HEADER_NAME, SHA_1_HEADER_NAME,
297 SHA_256_HEADER_NAME,
298 },
299 Crc32, Crc32c, Md5, Sha1, Sha256,
300 };
301
302 use crate::http::HttpChecksum;
303 use crate::ChecksumAlgorithm;
304 use aws_smithy_types::base64;
305 use http::HeaderValue;
306 use pretty_assertions::assert_eq;
307 use std::fmt::Write;
308
309 const TEST_DATA: &str = r#"test data"#;
310
311 fn base64_encoded_checksum_to_hex_string(header_value: &HeaderValue) -> String {
312 let decoded_checksum = base64::decode(header_value.to_str().unwrap()).unwrap();
313 let decoded_checksum = decoded_checksum
314 .into_iter()
315 .fold(String::new(), |mut acc, byte| {
316 write!(acc, "{byte:02X?}").expect("string will always be writeable");
317 acc
318 });
319
320 format!("0x{}", decoded_checksum)
321 }
322
323 #[test]
324 fn test_crc32_checksum() {
325 let mut checksum = Crc32::default();
326 checksum.update(TEST_DATA.as_bytes());
327 let checksum_result = Box::new(checksum).headers();
328 let encoded_checksum = checksum_result.get(CRC_32_HEADER_NAME).unwrap();
329 let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
330
331 let expected_checksum = "0xD308AEB2";
332
333 assert_eq!(decoded_checksum, expected_checksum);
334 }
335
336 #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
339 #[test]
340 fn test_crc32c_checksum() {
341 let mut checksum = Crc32c::default();
342 checksum.update(TEST_DATA.as_bytes());
343 let checksum_result = Box::new(checksum).headers();
344 let encoded_checksum = checksum_result.get(CRC_32_C_HEADER_NAME).unwrap();
345 let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
346
347 let expected_checksum = "0x3379B4CA";
348
349 assert_eq!(decoded_checksum, expected_checksum);
350 }
351
352 #[test]
353 fn test_sha1_checksum() {
354 let mut checksum = Sha1::default();
355 checksum.update(TEST_DATA.as_bytes());
356 let checksum_result = Box::new(checksum).headers();
357 let encoded_checksum = checksum_result.get(SHA_1_HEADER_NAME).unwrap();
358 let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
359
360 let expected_checksum = "0xF48DD853820860816C75D54D0F584DC863327A7C";
361
362 assert_eq!(decoded_checksum, expected_checksum);
363 }
364
365 #[test]
366 fn test_sha256_checksum() {
367 let mut checksum = Sha256::default();
368 checksum.update(TEST_DATA.as_bytes());
369 let checksum_result = Box::new(checksum).headers();
370 let encoded_checksum = checksum_result.get(SHA_256_HEADER_NAME).unwrap();
371 let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
372
373 let expected_checksum =
374 "0x916F0027A575074CE72A331777C3478D6513F786A591BD892DA1A577BF2335F9";
375
376 assert_eq!(decoded_checksum, expected_checksum);
377 }
378
379 #[test]
380 fn test_md5_checksum() {
381 let mut checksum = Md5::default();
382 checksum.update(TEST_DATA.as_bytes());
383 let checksum_result = Box::new(checksum).headers();
384 let encoded_checksum = checksum_result.get(MD5_HEADER_NAME).unwrap();
385 let decoded_checksum = base64_encoded_checksum_to_hex_string(encoded_checksum);
386
387 let expected_checksum = "0xEB733A00C0C9D336E65691A37AB54293";
388
389 assert_eq!(decoded_checksum, expected_checksum);
390 }
391
392 #[test]
393 fn test_checksum_algorithm_returns_error_for_unknown() {
394 let error = "some invalid checksum algorithm"
395 .parse::<ChecksumAlgorithm>()
396 .expect_err("it should error");
397 assert_eq!(
398 "some invalid checksum algorithm",
399 error.checksum_algorithm()
400 );
401 }
402}