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