aws_smithy_checksums/
http.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Checksum support for HTTP requests and responses.
7
8use aws_smithy_types::base64;
9use http::header::{HeaderMap, HeaderValue};
10
11use crate::Crc64Nvme;
12use crate::{
13    Checksum, Crc32, Crc32c, Md5, Sha1, Sha256, CRC_32_C_NAME, CRC_32_NAME, CRC_64_NVME_NAME,
14    SHA_1_NAME, SHA_256_NAME,
15};
16
17pub const CRC_32_HEADER_NAME: &str = "x-amz-checksum-crc32";
18pub const CRC_32_C_HEADER_NAME: &str = "x-amz-checksum-crc32c";
19pub const SHA_1_HEADER_NAME: &str = "x-amz-checksum-sha1";
20pub const SHA_256_HEADER_NAME: &str = "x-amz-checksum-sha256";
21pub const CRC_64_NVME_HEADER_NAME: &str = "x-amz-checksum-crc64nvme";
22
23// Preserved for compatibility purposes. This should never be used by users, only within smithy-rs
24#[warn(dead_code)]
25pub(crate) static MD5_HEADER_NAME: &str = "content-md5";
26
27/// When a response has to be checksum-verified, we have to check possible headers until we find the
28/// header with the precalculated checksum. Because a service may send back multiple headers, we have
29/// to check them in order based on how fast each checksum is to calculate.
30pub const CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER: [&str; 5] = [
31    CRC_64_NVME_NAME,
32    CRC_32_C_NAME,
33    CRC_32_NAME,
34    SHA_1_NAME,
35    SHA_256_NAME,
36];
37
38/// Checksum algorithms are use to validate the integrity of data. Structs that implement this trait
39/// can be used as checksum calculators. This trait requires Send + Sync because these checksums are
40/// often used in a threaded context.
41pub trait HttpChecksum: Checksum + Send + Sync {
42    /// Either return this checksum as a `HeaderMap` containing one HTTP header, or return an error
43    /// describing why checksum calculation failed.
44    fn headers(self: Box<Self>) -> HeaderMap<HeaderValue> {
45        let mut header_map = HeaderMap::new();
46        header_map.insert(self.header_name(), self.header_value());
47
48        header_map
49    }
50
51    /// Return the `HeaderName` used to represent this checksum algorithm
52    fn header_name(&self) -> &'static str;
53
54    /// Return the calculated checksum as a base64-encoded `HeaderValue`
55    fn header_value(self: Box<Self>) -> HeaderValue {
56        let hash = self.finalize();
57        HeaderValue::from_str(&base64::encode(&hash[..]))
58            .expect("base64 encoded bytes are always valid header values")
59    }
60
61    /// Return the total size of
62    /// - The `HeaderName`
63    /// - The header name/value separator
64    /// - The base64-encoded `HeaderValue`
65    fn size(&self) -> u64 {
66        let trailer_name_size_in_bytes = self.header_name().len();
67        let base64_encoded_checksum_size_in_bytes =
68            base64::encoded_length(Checksum::size(self) as usize);
69
70        let size = trailer_name_size_in_bytes
71            // HTTP trailer names and values may be separated by either a single colon or a single
72            // colon and a whitespace. In the AWS Rust SDK, we use a single colon.
73            + ":".len()
74            + base64_encoded_checksum_size_in_bytes;
75
76        size as u64
77    }
78}
79
80impl HttpChecksum for Crc32 {
81    fn header_name(&self) -> &'static str {
82        CRC_32_HEADER_NAME
83    }
84}
85
86impl HttpChecksum for Crc32c {
87    fn header_name(&self) -> &'static str {
88        CRC_32_C_HEADER_NAME
89    }
90}
91
92impl HttpChecksum for Crc64Nvme {
93    fn header_name(&self) -> &'static str {
94        CRC_64_NVME_HEADER_NAME
95    }
96}
97
98impl HttpChecksum for Sha1 {
99    fn header_name(&self) -> &'static str {
100        SHA_1_HEADER_NAME
101    }
102}
103
104impl HttpChecksum for Sha256 {
105    fn header_name(&self) -> &'static str {
106        SHA_256_HEADER_NAME
107    }
108}
109
110impl HttpChecksum for Md5 {
111    fn header_name(&self) -> &'static str {
112        MD5_HEADER_NAME
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use aws_smithy_types::base64;
119    use bytes::Bytes;
120
121    use crate::{
122        ChecksumAlgorithm, CRC_32_C_NAME, CRC_32_NAME, CRC_64_NVME_NAME, SHA_1_NAME, SHA_256_NAME,
123    };
124
125    use super::HttpChecksum;
126
127    #[test]
128    fn test_trailer_length_of_crc32_checksum_body() {
129        let checksum = CRC_32_NAME
130            .parse::<ChecksumAlgorithm>()
131            .unwrap()
132            .into_impl();
133        let expected_size = 29;
134        let actual_size = HttpChecksum::size(&*checksum);
135        assert_eq!(expected_size, actual_size)
136    }
137
138    #[test]
139    fn test_trailer_value_of_crc32_checksum_body() {
140        let checksum = CRC_32_NAME
141            .parse::<ChecksumAlgorithm>()
142            .unwrap()
143            .into_impl();
144        // The CRC32 of an empty string is all zeroes
145        let expected_value = Bytes::from_static(b"\0\0\0\0");
146        let expected_value = base64::encode(&expected_value);
147        let actual_value = checksum.header_value();
148        assert_eq!(expected_value, actual_value)
149    }
150
151    #[test]
152    fn test_trailer_length_of_crc32c_checksum_body() {
153        let checksum = CRC_32_C_NAME
154            .parse::<ChecksumAlgorithm>()
155            .unwrap()
156            .into_impl();
157        let expected_size = 30;
158        let actual_size = HttpChecksum::size(&*checksum);
159        assert_eq!(expected_size, actual_size)
160    }
161
162    #[test]
163    fn test_trailer_value_of_crc32c_checksum_body() {
164        let checksum = CRC_32_C_NAME
165            .parse::<ChecksumAlgorithm>()
166            .unwrap()
167            .into_impl();
168        // The CRC32C of an empty string is all zeroes
169        let expected_value = Bytes::from_static(b"\0\0\0\0");
170        let expected_value = base64::encode(&expected_value);
171        let actual_value = checksum.header_value();
172        assert_eq!(expected_value, actual_value)
173    }
174
175    #[test]
176    fn test_trailer_length_of_crc64nvme_checksum_body() {
177        let checksum = CRC_64_NVME_NAME
178            .parse::<ChecksumAlgorithm>()
179            .unwrap()
180            .into_impl();
181        let expected_size = 37;
182        let actual_size = HttpChecksum::size(&*checksum);
183        assert_eq!(expected_size, actual_size)
184    }
185
186    #[test]
187    fn test_trailer_value_of_crc64nvme_checksum_body() {
188        let checksum = CRC_64_NVME_NAME
189            .parse::<ChecksumAlgorithm>()
190            .unwrap()
191            .into_impl();
192        // The CRC64NVME of an empty string is all zeroes
193        let expected_value = Bytes::from_static(b"\0\0\0\0\0\0\0\0");
194        let expected_value = base64::encode(&expected_value);
195        let actual_value = checksum.header_value();
196        assert_eq!(expected_value, actual_value)
197    }
198
199    #[test]
200    fn test_trailer_length_of_sha1_checksum_body() {
201        let checksum = SHA_1_NAME.parse::<ChecksumAlgorithm>().unwrap().into_impl();
202        let expected_size = 48;
203        let actual_size = HttpChecksum::size(&*checksum);
204        assert_eq!(expected_size, actual_size)
205    }
206
207    #[test]
208    fn test_trailer_value_of_sha1_checksum_body() {
209        let checksum = SHA_1_NAME.parse::<ChecksumAlgorithm>().unwrap().into_impl();
210        // The SHA1 of an empty string is da39a3ee5e6b4b0d3255bfef95601890afd80709
211        let expected_value = Bytes::from_static(&[
212            0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
213            0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
214        ]);
215        let expected_value = base64::encode(&expected_value);
216        let actual_value = checksum.header_value();
217        assert_eq!(expected_value, actual_value)
218    }
219
220    #[test]
221    fn test_trailer_length_of_sha256_checksum_body() {
222        let checksum = SHA_256_NAME
223            .parse::<ChecksumAlgorithm>()
224            .unwrap()
225            .into_impl();
226        let expected_size = 66;
227        let actual_size = HttpChecksum::size(&*checksum);
228        assert_eq!(expected_size, actual_size)
229    }
230
231    #[test]
232    fn test_trailer_value_of_sha256_checksum_body() {
233        let checksum = SHA_256_NAME
234            .parse::<ChecksumAlgorithm>()
235            .unwrap()
236            .into_impl();
237        // The SHA256 of an empty string is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
238        let expected_value = Bytes::from_static(&[
239            0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
240            0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
241            0x78, 0x52, 0xb8, 0x55,
242        ]);
243        let expected_value = base64::encode(&expected_value);
244        let actual_value = checksum.header_value();
245        assert_eq!(expected_value, actual_value)
246    }
247}