// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::base64; use http::header::{HeaderMap, HeaderValue}; use crate::Crc64Nvme; use crate::{CRC_32_C_NAME, CRC_32_NAME, CRC_64_NVME_NAME, Checksum, Crc32, Crc32c, Md5, SHA_1_NAME, SHA_256_NAME, Sha1, Sha256}; pub const CRC_32_HEADER_NAME: &str = "x-amz-checksum-crc32"; pub const CRC_32_C_HEADER_NAME: &str = "x-amz-checksum-crc32c"; pub const SHA_1_HEADER_NAME: &str = "x-amz-checksum-sha1"; pub const SHA_256_HEADER_NAME: &str = "x-amz-checksum-sha256"; pub const CRC_64_NVME_HEADER_NAME: &str = "x-amz-checksum-crc64nvme"; #[allow(dead_code)] pub(crate) static MD5_HEADER_NAME: &str = "content-md5"; pub const CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER: [&str; 5] = [CRC_64_NVME_NAME, CRC_32_C_NAME, CRC_32_NAME, SHA_1_NAME, SHA_256_NAME]; pub trait HttpChecksum: Checksum + Send + Sync { fn headers(self: Box) -> HeaderMap { let mut header_map = HeaderMap::new(); header_map.insert(self.header_name(), self.header_value()); header_map } fn header_name(&self) -> &'static str; fn header_value(self: Box) -> HeaderValue { let hash = self.finalize(); HeaderValue::from_str(&base64::encode(&hash[..])).expect("base64 encoded bytes are always valid header values") } fn size(&self) -> u64 { let trailer_name_size_in_bytes = self.header_name().len(); let base64_encoded_checksum_size_in_bytes = base64::encoded_length(Checksum::size(self) as usize); let size = trailer_name_size_in_bytes + ":".len() + base64_encoded_checksum_size_in_bytes; size as u64 } } impl HttpChecksum for Crc32 { fn header_name(&self) -> &'static str { CRC_32_HEADER_NAME } } impl HttpChecksum for Crc32c { fn header_name(&self) -> &'static str { CRC_32_C_HEADER_NAME } } impl HttpChecksum for Crc64Nvme { fn header_name(&self) -> &'static str { CRC_64_NVME_HEADER_NAME } } impl HttpChecksum for Sha1 { fn header_name(&self) -> &'static str { SHA_1_HEADER_NAME } } impl HttpChecksum for Sha256 { fn header_name(&self) -> &'static str { SHA_256_HEADER_NAME } } impl HttpChecksum for Md5 { fn header_name(&self) -> &'static str { MD5_HEADER_NAME } } #[cfg(test)] mod tests { use crate::base64; use bytes::Bytes; use crate::{CRC_32_C_NAME, CRC_32_NAME, CRC_64_NVME_NAME, ChecksumAlgorithm, SHA_1_NAME, SHA_256_NAME}; use super::HttpChecksum; #[test] fn test_trailer_length_of_crc32_checksum_body() { let checksum = CRC_32_NAME.parse::().unwrap().into_impl(); let expected_size = 29; let actual_size = HttpChecksum::size(&*checksum); assert_eq!(expected_size, actual_size) } #[test] fn test_trailer_value_of_crc32_checksum_body() { let checksum = CRC_32_NAME.parse::().unwrap().into_impl(); // The CRC32 of an empty string is all zeroes let expected_value = Bytes::from_static(b"\0\0\0\0"); let expected_value = base64::encode(&expected_value); let actual_value = checksum.header_value(); assert_eq!(expected_value, actual_value) } #[test] fn test_trailer_length_of_crc32c_checksum_body() { let checksum = CRC_32_C_NAME.parse::().unwrap().into_impl(); let expected_size = 30; let actual_size = HttpChecksum::size(&*checksum); assert_eq!(expected_size, actual_size) } #[test] fn test_trailer_value_of_crc32c_checksum_body() { let checksum = CRC_32_C_NAME.parse::().unwrap().into_impl(); // The CRC32C of an empty string is all zeroes let expected_value = Bytes::from_static(b"\0\0\0\0"); let expected_value = base64::encode(&expected_value); let actual_value = checksum.header_value(); assert_eq!(expected_value, actual_value) } #[test] fn test_trailer_length_of_crc64nvme_checksum_body() { let checksum = CRC_64_NVME_NAME.parse::().unwrap().into_impl(); let expected_size = 37; let actual_size = HttpChecksum::size(&*checksum); assert_eq!(expected_size, actual_size) } #[test] fn test_trailer_value_of_crc64nvme_checksum_body() { let checksum = CRC_64_NVME_NAME.parse::().unwrap().into_impl(); // The CRC64NVME of an empty string is all zeroes let expected_value = Bytes::from_static(b"\0\0\0\0\0\0\0\0"); let expected_value = base64::encode(&expected_value); let actual_value = checksum.header_value(); assert_eq!(expected_value, actual_value) } #[test] fn test_trailer_length_of_sha1_checksum_body() { let checksum = SHA_1_NAME.parse::().unwrap().into_impl(); let expected_size = 48; let actual_size = HttpChecksum::size(&*checksum); assert_eq!(expected_size, actual_size) } #[test] fn test_trailer_value_of_sha1_checksum_body() { let checksum = SHA_1_NAME.parse::().unwrap().into_impl(); // The SHA1 of an empty string is da39a3ee5e6b4b0d3255bfef95601890afd80709 let expected_value = Bytes::from_static(&[ 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09, ]); let expected_value = base64::encode(&expected_value); let actual_value = checksum.header_value(); assert_eq!(expected_value, actual_value) } #[test] fn test_trailer_length_of_sha256_checksum_body() { let checksum = SHA_256_NAME.parse::().unwrap().into_impl(); let expected_size = 66; let actual_size = HttpChecksum::size(&*checksum); assert_eq!(expected_size, actual_size) } #[test] fn test_trailer_value_of_sha256_checksum_body() { let checksum = SHA_256_NAME.parse::().unwrap().into_impl(); let expected_value = Bytes::from_static(&[ 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, ]); let expected_value = base64::encode(&expected_value); let actual_value = checksum.header_value(); assert_eq!(expected_value, actual_value) } }