Merge pull request 'implement x-amz-checksum-type' (#1025) from checksum-type into next-v2

Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1025
This commit is contained in:
Alex
2025-05-22 17:06:04 +00:00
18 changed files with 593 additions and 179 deletions
+1 -3
View File
@@ -21,9 +21,7 @@ garage_util.workspace = true
base64.workspace = true
bytes.workspace = true
chrono.workspace = true
crc32fast.workspace = true
crc32c.workspace = true
crc64fast-nvme.workspace = true
crc-fast.workspace = true
crypto-common.workspace = true
err-derive.workspace = true
hex.workspace = true
+40 -24
View File
@@ -1,10 +1,7 @@
use std::convert::{TryFrom, TryInto};
use std::hash::Hasher;
use std::convert::TryInto;
use base64::prelude::*;
use crc32c::Crc32cHasher as Crc32c;
use crc32fast::Hasher as Crc32;
use crc64fast_nvme::Digest as Crc64Nvme;
use crc_fast::{CrcAlgorithm, Digest as CrcDigest};
use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::Sha256;
@@ -22,6 +19,7 @@ pub const CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5");
pub const X_AMZ_CHECKSUM_ALGORITHM: HeaderName =
HeaderName::from_static("x-amz-checksum-algorithm");
pub const X_AMZ_CHECKSUM_MODE: HeaderName = HeaderName::from_static("x-amz-checksum-mode");
pub const X_AMZ_CHECKSUM_TYPE: HeaderName = HeaderName::from_static("x-amz-checksum-type");
pub const X_AMZ_CHECKSUM_CRC32: HeaderName = HeaderName::from_static("x-amz-checksum-crc32");
pub const X_AMZ_CHECKSUM_CRC32C: HeaderName = HeaderName::from_static("x-amz-checksum-crc32c");
pub const X_AMZ_CHECKSUM_CRC64NVME: HeaderName =
@@ -29,6 +27,10 @@ pub const X_AMZ_CHECKSUM_CRC64NVME: HeaderName =
pub const X_AMZ_CHECKSUM_SHA1: HeaderName = HeaderName::from_static("x-amz-checksum-sha1");
pub const X_AMZ_CHECKSUM_SHA256: HeaderName = HeaderName::from_static("x-amz-checksum-sha256");
// Values for x-amz-checksum-type
pub const COMPOSITE: &str = "COMPOSITE";
pub const FULL_OBJECT: &str = "FULL_OBJECT";
pub type Crc32Checksum = [u8; 4];
pub type Crc32cChecksum = [u8; 4];
pub type Crc64NvmeChecksum = [u8; 8];
@@ -36,6 +38,21 @@ pub type Md5Checksum = [u8; 16];
pub type Sha1Checksum = [u8; 20];
pub type Sha256Checksum = [u8; 32];
// -- MAP OF CRC ALGORITHMS :
// CRC32 -> CrcAlgorithm::Crc32IsoHdlc
// CRC32C -> CrcAlgorithm::Crc32Iscsi
// CRC64NVME -> CrcAlgorithm::Crc64Nvme
pub fn new_crc32() -> CrcDigest {
CrcDigest::new(CrcAlgorithm::Crc32IsoHdlc)
}
pub fn new_crc32c() -> CrcDigest {
CrcDigest::new(CrcAlgorithm::Crc32Iscsi)
}
pub fn new_crc64nvme() -> CrcDigest {
CrcDigest::new(CrcAlgorithm::Crc64Nvme)
}
#[derive(Debug, Default, Clone)]
pub struct ExpectedChecksums {
// base64-encoded md5 (content-md5 header)
@@ -47,9 +64,9 @@ pub struct ExpectedChecksums {
}
pub struct Checksummer {
pub crc32: Option<Crc32>,
pub crc32c: Option<Crc32c>,
pub crc64nvme: Option<Crc64Nvme>,
pub crc32: Option<CrcDigest>,
pub crc32c: Option<CrcDigest>,
pub crc64nvme: Option<CrcDigest>,
pub md5: Option<Md5>,
pub sha1: Option<Sha1>,
pub sha256: Option<Sha256>,
@@ -98,13 +115,13 @@ impl Checksummer {
self.sha256 = Some(Sha256::new());
}
if matches!(&expected.extra, Some(ChecksumValue::Crc32(_))) {
self.crc32 = Some(Crc32::new());
self.crc32 = Some(new_crc32());
}
if matches!(&expected.extra, Some(ChecksumValue::Crc32c(_))) {
self.crc32c = Some(Crc32c::default());
self.crc32c = Some(new_crc32c());
}
if matches!(&expected.extra, Some(ChecksumValue::Crc64Nvme(_))) {
self.crc64nvme = Some(Crc64Nvme::default());
self.crc64nvme = Some(new_crc64nvme());
}
if matches!(&expected.extra, Some(ChecksumValue::Sha1(_))) {
self.sha1 = Some(Sha1::new());
@@ -114,13 +131,13 @@ impl Checksummer {
pub fn add(mut self, algo: Option<ChecksumAlgorithm>) -> Self {
match algo {
Some(ChecksumAlgorithm::Crc32) => {
self.crc32 = Some(Crc32::new());
self.crc32 = Some(new_crc32());
}
Some(ChecksumAlgorithm::Crc32c) => {
self.crc32c = Some(Crc32c::default());
self.crc32c = Some(new_crc32c());
}
Some(ChecksumAlgorithm::Crc64Nvme) => {
self.crc64nvme = Some(Crc64Nvme::default());
self.crc64nvme = Some(new_crc64nvme());
}
Some(ChecksumAlgorithm::Sha1) => {
self.sha1 = Some(Sha1::new());
@@ -138,10 +155,10 @@ impl Checksummer {
crc32.update(bytes);
}
if let Some(crc32c) = &mut self.crc32c {
crc32c.write(bytes);
crc32c.update(bytes);
}
if let Some(crc64nvme) = &mut self.crc64nvme {
crc64nvme.write(bytes);
crc64nvme.update(bytes);
}
if let Some(md5) = &mut self.md5 {
md5.update(bytes);
@@ -156,11 +173,9 @@ impl Checksummer {
pub fn finalize(self) -> Checksums {
Checksums {
crc32: self.crc32.map(|x| u32::to_be_bytes(x.finalize())),
crc32c: self
.crc32c
.map(|x| u32::to_be_bytes(u32::try_from(x.finish()).unwrap())),
crc64nvme: self.crc64nvme.map(|x| u64::to_be_bytes(x.sum64())),
crc32: self.crc32.map(|x| u32::to_be_bytes(x.finalize() as u32)),
crc32c: self.crc32c.map(|x| u32::to_be_bytes(x.finalize() as u32)),
crc64nvme: self.crc64nvme.map(|x| u64::to_be_bytes(x.finalize())),
md5: self.md5.map(|x| x.finalize()[..].try_into().unwrap()),
sha1: self.sha1.map(|x| x.finalize()[..].try_into().unwrap()),
sha256: self.sha256.map(|x| x.finalize()[..].try_into().unwrap()),
@@ -192,10 +207,11 @@ impl Checksums {
}
if let Some(extra) = expected.extra {
let algo = extra.algorithm();
if self.extract(Some(algo)) != Some(extra) {
let calculated = self.extract(Some(algo));
if calculated != Some(extra) {
return Err(Error::InvalidDigest(format!(
"Failed to validate checksum for algorithm {:?}",
algo
"Failed to validate checksum for algorithm {:?}: calculated {:?}, expected {:?}",
algo, calculated, extra
)));
}
}
+1 -3
View File
@@ -27,9 +27,7 @@ async-compression.workspace = true
base64.workspace = true
bytes.workspace = true
chrono.workspace = true
crc32fast.workspace = true
crc32c.workspace = true
crc64fast-nvme.workspace = true
crc-fast.workspace = true
err-derive.workspace = true
hex.workspace = true
hmac.workspace = true
+33 -14
View File
@@ -78,8 +78,24 @@ pub async fn handle_copy(
},
)?;
let was_multipart = source_version_meta.etag.contains('-') // HACK
|| source_object_meta_inner.checksum_type == Some(ChecksumType::Composite);
// Extract source checksum info before source_object_meta_inner is consumed
let source_checksum = source_object_meta_inner.checksum;
let source_checksum_type = match (source_object_meta_inner.checksum_type, source_checksum) {
(Some(ct), _) => Some(ct),
(None, Some(_)) => {
// Migrated object from garage v1.x or older
// determine checksum type depending if this is a multipart upload or not
if was_multipart {
Some(ChecksumType::Composite)
} else {
Some(ChecksumType::FullObject)
}
}
(None, None) => None,
};
let source_checksum_algorithm = source_checksum.map(|x| x.algorithm());
// If source object has a checksum, the destination object must as well.
@@ -88,7 +104,6 @@ pub async fn handle_copy(
let checksum_algorithm = checksum_algorithm.or(source_checksum_algorithm);
// Determine metadata of destination object
let was_multipart = source_version_meta.etag.contains('-');
let dest_object_meta = ObjectVersionMetaInner {
headers: match req.headers().get("x-amz-metadata-directive") {
Some(v) if v == hyper::header::HeaderValue::from_static("REPLACE") => {
@@ -97,6 +112,7 @@ pub async fn handle_copy(
_ => source_object_meta_inner.into_owned().headers,
},
checksum: source_checksum,
checksum_type: source_checksum_type,
};
// Do actual object copying
@@ -116,8 +132,8 @@ pub async fn handle_copy(
// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html
let must_recopy = !EncryptionParams::is_same(&source_encryption, &dest_encryption)
|| source_checksum_algorithm != checksum_algorithm
|| (was_multipart && checksum_algorithm.is_some());
|| (checksum_algorithm.is_some()
&& (was_multipart || checksum_algorithm != source_checksum_algorithm));
let res = if !must_recopy {
// In most cases, we can just copy the metadata and link blocks of the
@@ -134,18 +150,21 @@ pub async fn handle_copy(
)
.await?
} else {
let expected_checksum = ExpectedChecksums {
md5: None,
sha256: None,
extra: source_checksum,
};
let checksum_mode = if was_multipart || source_checksum_algorithm != checksum_algorithm {
ChecksumMode::Calculate(checksum_algorithm)
} else {
ChecksumMode::Verify(&expected_checksum)
ChecksumMode::Verify(ExpectedChecksums {
md5: None,
sha256: None,
extra: source_checksum,
})
};
// For multipart uploads that had a composite checksum, set checksum type
// to full object as it will be recalculated.
let dest_object_meta = ObjectVersionMetaInner {
checksum_type: checksum_algorithm.map(|_| ChecksumType::FullObject),
..dest_object_meta
};
// If source and dest encryption use different keys,
// we must decrypt content and re-encrypt, so rewrite all data blocks.
handle_copy_reencrypt(
ctx,
dest_key,
@@ -325,7 +344,7 @@ async fn handle_copy_reencrypt(
source_version: &ObjectVersion,
source_version_data: &ObjectVersionData,
source_encryption: EncryptionParams,
checksum_mode: ChecksumMode<'_>,
checksum_mode: ChecksumMode,
) -> Result<SaveStreamResult, Error> {
// basically we will read the source data (decrypt if necessary)
// and save that in a new object (encrypt if necessary),
@@ -512,7 +531,7 @@ pub async fn handle_upload_part_copy(
// Now, actually copy the blocks
let mut checksummer = Checksummer::init(&Default::default(), !dest_encryption.is_encrypted())
.add(dest_object_checksum_algorithm);
.add(dest_object_checksum_algorithm.map(|(algo, _)| algo));
// First, create a stream that is able to read the source blocks
// and extract the subrange if necessary.
@@ -662,7 +681,7 @@ pub async fn handle_upload_part_copy(
let checksums = checksummer.finalize();
let etag = dest_encryption.etag_from_md5(&checksums.md5);
let checksum = checksums.extract(dest_object_checksum_algorithm);
let checksum = checksums.extract(dest_object_checksum_algorithm.map(|(algo, _)| algo));
// Put the part's ETag in the Versiontable
dest_mpu.parts.put(
+1
View File
@@ -1002,6 +1002,7 @@ mod tests {
inner: ObjectVersionMetaInner {
headers: vec![],
checksum: None,
checksum_type: None,
},
},
checksum_algorithm: None,
+192 -89
View File
@@ -1,14 +1,11 @@
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::hash::Hasher;
use std::convert::TryInto;
use std::sync::Arc;
use base64::prelude::*;
use crc32c::Crc32cHasher as Crc32c;
use crc32fast::Hasher as Crc32;
use crc64fast_nvme::Digest as Crc64Nvme;
use crc_fast::{CrcAlgorithm, Digest as CrcDigest};
use futures::prelude::*;
use hyper::{Request, Response};
use hyper::{header::HeaderValue, HeaderMap, Request, Response};
use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::Sha256;
@@ -54,6 +51,7 @@ pub async fn handle_create_multipart_upload(
let meta = ObjectVersionMetaInner {
headers,
checksum: None,
checksum_type: None,
};
// Determine whether object should be encrypted, and if so the key
@@ -68,7 +66,10 @@ pub async fn handle_create_multipart_upload(
)?;
let object_encryption = encryption.encrypt_meta(meta)?;
let checksum_algorithm = request_checksum_algorithm(req.headers())?;
let checksum_algorithm = request_checksum_algorithm_and_type(
req.headers(),
request_checksum_algorithm(req.headers())?,
)?;
// Create object in object table
let object_version = ObjectVersion {
@@ -227,7 +228,7 @@ pub async fn handle_put_part(
MpuPart {
version: version_uuid,
etag: Some(etag.clone()),
checksum: checksums.extract(checksum_algorithm),
checksum: checksums.extract(checksum_algorithm.map(|(algo, _)| algo)),
size: Some(total_size),
},
);
@@ -289,6 +290,14 @@ pub async fn handle_complete_multipart_upload(
let (req_head, req_body) = req.into_parts();
let expected_checksum = request_checksum_value(&req_head.headers)?;
let req_checksum_algorithm = request_checksum_algorithm_and_type(
&req_head.headers,
expected_checksum.map(|x| x.algorithm()),
)?;
debug!(
"CompleteMultipartUpload expected checksum: {:?}, request checksum type: {:?}",
expected_checksum, req_checksum_algorithm
);
let body = req_body.collect().await?;
@@ -319,6 +328,17 @@ pub async fn handle_complete_multipart_upload(
} => (encryption, checksum_algorithm),
_ => unreachable!(),
};
debug!(
"CompleteMultipartUpload object checksum_algorithm: {:?}",
checksum_algorithm
);
if req_checksum_algorithm.is_some() && req_checksum_algorithm != checksum_algorithm {
return Err(Error::InvalidDigest(format!(
"checksum algorithm {:?} does not correspond to algorithm specified in CreateMultipartUpload {:?}",
req_checksum_algorithm,
checksum_algorithm
)));
}
// Check that part numbers are an increasing sequence.
// (it doesn't need to start at 1 nor to be a continuous sequence,
@@ -344,8 +364,7 @@ pub async fn handle_complete_multipart_upload(
for req_part in body_list_of_parts.iter() {
match have_parts.get(&req_part.part_number) {
Some(part) if part.etag.as_ref() == Some(&req_part.etag) && part.size.is_some() => {
// alternative version: if req_part.checksum.is_some() && part.checksum != req_part.checksum {
if part.checksum != req_part.checksum {
if req_part.checksum.is_some() && part.checksum != req_part.checksum {
return Err(Error::InvalidDigest(format!(
"Invalid checksum for part {}: in request = {:?}, uploaded part = {:?}",
req_part.part_number, req_part.checksum, part.checksum
@@ -404,7 +423,11 @@ pub async fn handle_complete_multipart_upload(
// https://teppen.io/2018/06/23/aws_s3_etags/
let mut checksummer = MultipartChecksummer::init(checksum_algorithm);
for part in parts.iter() {
checksummer.update(part.etag.as_ref().unwrap(), part.checksum)?;
checksummer.update(
part.etag.as_ref().unwrap(),
part.checksum,
part.size.unwrap(),
)?;
}
let (checksum_md5, checksum_extra) = checksummer.finalize();
@@ -440,6 +463,7 @@ pub async fn handle_complete_multipart_upload(
let new_meta = ObjectVersionMetaInner {
headers: meta.into_owned().headers,
checksum: checksum_extra,
checksum_type: checksum_algorithm.map(|(_, ty)| ty),
};
encryption.encrypt_meta(new_meta)?
}
@@ -494,6 +518,11 @@ pub async fn handle_complete_multipart_upload(
Some(ChecksumValue::Sha256(x)) => Some(s3_xml::Value(BASE64_STANDARD.encode(&x))),
_ => None,
},
checksum_type: match checksum_algorithm {
Some((_, ChecksumType::Composite)) => Some(s3_xml::Value(COMPOSITE.into())),
Some((_, ChecksumType::FullObject)) => Some(s3_xml::Value(FULL_OBJECT.into())),
None => None,
},
};
let xml = s3_xml::to_xml_with_header(&result)?;
@@ -649,40 +678,52 @@ fn parse_complete_multipart_upload_body(
// ====== checksummer ====
pub fn request_checksum_algorithm_and_type(
headers: &HeaderMap<HeaderValue>,
algo: Option<ChecksumAlgorithm>,
) -> Result<Option<(ChecksumAlgorithm, ChecksumType)>, Error> {
match (headers.get(X_AMZ_CHECKSUM_TYPE), algo) {
(None, None) => Ok(None),
(None, Some(algo)) => {
let ty = match algo {
ChecksumAlgorithm::Crc64Nvme => ChecksumType::FullObject,
_ => ChecksumType::Composite,
};
Ok(Some((algo, ty)))
}
(Some(_), None) => Err(Error::bad_request(
"Cannot specify x-amz-checksum-type when no checksum algorithm is in use.",
)),
(Some(x), Some(algo)) => {
let checksum_type = match x.as_bytes() {
x if x == COMPOSITE.as_bytes() => ChecksumType::Composite,
x if x == FULL_OBJECT.as_bytes() => ChecksumType::FullObject,
_ => return Err(Error::bad_request("Invalid x-amz-checksum-type value")),
};
match (checksum_type, algo) {
(ChecksumType::Composite, ChecksumAlgorithm::Crc64Nvme)
| (ChecksumType::FullObject, ChecksumAlgorithm::Sha1)
| (ChecksumType::FullObject, ChecksumAlgorithm::Sha256) => Err(Error::bad_request(format!(
"checksum type {:?} is not supported for algorithm {:?}",
checksum_type, algo
))),
(ty, algo) => Ok(Some((algo, ty))),
}
}
}
}
#[derive(Default)]
pub(crate) struct MultipartChecksummer {
pub md5: Md5,
pub extra: Option<MultipartExtraChecksummer>,
}
pub(crate) enum MultipartExtraChecksummer {
Crc32(Crc32),
Crc32c(Crc32c),
Crc64Nvme(Crc64Nvme),
Sha1(Sha1),
Sha256(Sha256),
}
impl MultipartChecksummer {
pub(crate) fn init(algo: Option<ChecksumAlgorithm>) -> Self {
pub(crate) fn init(algo: Option<(ChecksumAlgorithm, ChecksumType)>) -> Self {
Self {
md5: Md5::new(),
extra: match algo {
None => None,
Some(ChecksumAlgorithm::Crc32) => {
Some(MultipartExtraChecksummer::Crc32(Crc32::new()))
}
Some(ChecksumAlgorithm::Crc32c) => {
Some(MultipartExtraChecksummer::Crc32c(Crc32c::default()))
}
Some(ChecksumAlgorithm::Crc64Nvme) => {
Some(MultipartExtraChecksummer::Crc64Nvme(Crc64Nvme::default()))
}
Some(ChecksumAlgorithm::Sha1) => Some(MultipartExtraChecksummer::Sha1(Sha1::new())),
Some(ChecksumAlgorithm::Sha256) => {
Some(MultipartExtraChecksummer::Sha256(Sha256::new()))
}
},
extra: algo.map(|(algo, cktype)| MultipartExtraChecksummer::init(algo, cktype)),
}
}
@@ -690,68 +731,130 @@ impl MultipartChecksummer {
&mut self,
etag: &str,
checksum: Option<ChecksumValue>,
part_len: u64,
) -> Result<(), Error> {
self.md5
.update(&hex::decode(&etag).ok_or_message("invalid etag hex")?);
match (&mut self.extra, checksum) {
(None, _) => (),
(
Some(MultipartExtraChecksummer::Crc32(ref mut crc32)),
Some(ChecksumValue::Crc32(x)),
) => {
crc32.update(&x);
}
(
Some(MultipartExtraChecksummer::Crc32c(ref mut crc32c)),
Some(ChecksumValue::Crc32c(x)),
) => {
crc32c.write(&x);
}
(
Some(MultipartExtraChecksummer::Crc64Nvme(ref mut crc64nvme)),
Some(ChecksumValue::Crc64Nvme(x)),
) => {
crc64nvme.write(&x);
}
(Some(MultipartExtraChecksummer::Sha1(ref mut sha1)), Some(ChecksumValue::Sha1(x))) => {
sha1.update(&x);
}
(
Some(MultipartExtraChecksummer::Sha256(ref mut sha256)),
Some(ChecksumValue::Sha256(x)),
) => {
sha256.update(&x);
}
(Some(_), b) => {
return Err(Error::internal_error(format!(
"part checksum was not computed correctly, got: {:?}",
b
)))
}
if let Some(extra) = &mut self.extra {
extra.update(checksum, part_len)?;
}
Ok(())
}
pub(crate) fn finalize(self) -> (Md5Checksum, Option<ChecksumValue>) {
let md5 = self.md5.finalize()[..].try_into().unwrap();
let extra = match self.extra {
None => None,
Some(MultipartExtraChecksummer::Crc32(crc32)) => {
Some(ChecksumValue::Crc32(u32::to_be_bytes(crc32.finalize())))
}
Some(MultipartExtraChecksummer::Crc32c(crc32c)) => Some(ChecksumValue::Crc32c(
u32::to_be_bytes(u32::try_from(crc32c.finish()).unwrap()),
)),
Some(MultipartExtraChecksummer::Crc64Nvme(crc64nvme)) => Some(
ChecksumValue::Crc64Nvme(u64::to_be_bytes(crc64nvme.sum64())),
),
Some(MultipartExtraChecksummer::Sha1(sha1)) => {
Some(ChecksumValue::Sha1(sha1.finalize()[..].try_into().unwrap()))
}
Some(MultipartExtraChecksummer::Sha256(sha256)) => Some(ChecksumValue::Sha256(
sha256.finalize()[..].try_into().unwrap(),
)),
};
let extra = self.extra.map(|c| c.finalize());
(md5, extra)
}
}
pub(crate) enum MultipartExtraChecksummer {
FullObjectCrc(CrcAlgorithm, Option<u64>),
CompositeCrc(ChecksumAlgorithm, CrcDigest),
CompositeSha1(Sha1),
CompositeSha256(Sha256),
}
impl MultipartExtraChecksummer {
fn init(algo: ChecksumAlgorithm, cktype: ChecksumType) -> Self {
match (algo, cktype) {
(algo, ChecksumType::FullObject) => {
let crc_type = match algo {
ChecksumAlgorithm::Crc32 => CrcAlgorithm::Crc32IsoHdlc,
ChecksumAlgorithm::Crc32c => CrcAlgorithm::Crc32Iscsi,
ChecksumAlgorithm::Crc64Nvme => CrcAlgorithm::Crc64Nvme,
_ => unreachable!(),
};
Self::FullObjectCrc(crc_type, None)
}
(ChecksumAlgorithm::Crc32, ChecksumType::Composite) => {
Self::CompositeCrc(ChecksumAlgorithm::Crc32, new_crc32())
}
(ChecksumAlgorithm::Crc32c, ChecksumType::Composite) => {
Self::CompositeCrc(ChecksumAlgorithm::Crc32c, new_crc32c())
}
(ChecksumAlgorithm::Sha1, ChecksumType::Composite) => Self::CompositeSha1(Sha1::new()),
(ChecksumAlgorithm::Sha256, ChecksumType::Composite) => {
Self::CompositeSha256(Sha256::new())
}
_ => unreachable!(),
}
}
fn update(&mut self, checksum: Option<ChecksumValue>, part_len: u64) -> Result<(), Error> {
match (self, checksum) {
(Self::FullObjectCrc(crc_algo, crc_value), Some(ck)) => {
let ck_u64 = match ck {
ChecksumValue::Crc32(x) => u32::from_be_bytes(x) as u64,
ChecksumValue::Crc32c(x) => u32::from_be_bytes(x) as u64,
ChecksumValue::Crc64Nvme(x) => u64::from_be_bytes(x),
_ => {
return Err(Error::internal_error(format!(
"part checksum was not computed correctly, got: {:?}",
ck
)))
}
};
*crc_value = match *crc_value {
None => Some(ck_u64),
Some(prev) => Some(crc_fast::checksum_combine(
*crc_algo, prev, ck_u64, part_len,
)),
};
}
(Self::CompositeCrc(_, digest), Some(ck)) => match ck {
ChecksumValue::Crc32(x) => digest.update(&x),
ChecksumValue::Crc32c(x) => digest.update(&x),
ChecksumValue::Crc64Nvme(x) => digest.update(&x),
_ => {
return Err(Error::internal_error(format!(
"part checksum was not computed correctly, got: {:?}",
ck
)))
}
},
(Self::CompositeSha1(sha1), Some(ChecksumValue::Sha1(x))) => {
sha1.update(&x);
}
(Self::CompositeSha256(sha256), Some(ChecksumValue::Sha256(x))) => {
sha256.update(&x);
}
_ => {
return Err(Error::internal_error(format!(
"part checksum was not computed correctly, got: {:?}",
checksum
)))
}
}
Ok(())
}
fn finalize(self) -> ChecksumValue {
match self {
Self::FullObjectCrc(algo, value) => match (algo, value) {
(CrcAlgorithm::Crc32IsoHdlc, Some(v)) => {
ChecksumValue::Crc32(u32::to_be_bytes(v as u32))
}
(CrcAlgorithm::Crc32Iscsi, Some(v)) => {
ChecksumValue::Crc32c(u32::to_be_bytes(v as u32))
}
(CrcAlgorithm::Crc64Nvme, Some(v)) => ChecksumValue::Crc64Nvme(u64::to_be_bytes(v)),
_ => unreachable!(),
},
Self::CompositeCrc(algo, crc) => match algo {
ChecksumAlgorithm::Crc32 => {
ChecksumValue::Crc32(u32::to_be_bytes(crc.finalize() as u32))
}
ChecksumAlgorithm::Crc32c => {
ChecksumValue::Crc32c(u32::to_be_bytes(crc.finalize() as u32))
}
_ => unreachable!(),
},
Self::CompositeSha1(sha1) => {
ChecksumValue::Sha1(sha1.finalize()[..].try_into().unwrap())
}
Self::CompositeSha256(sha256) => {
ChecksumValue::Sha256(sha256.finalize()[..].try_into().unwrap())
}
}
}
}
+2 -1
View File
@@ -233,6 +233,7 @@ pub async fn handle_post_object(
let meta = ObjectVersionMetaInner {
headers,
checksum: expected_checksums.extra,
checksum_type: expected_checksums.extra.map(|_| ChecksumType::FullObject),
};
let encryption = EncryptionParams::new_from_headers(
@@ -261,7 +262,7 @@ pub async fn handle_post_object(
encryption,
StreamLimiter::new(stream, conditions.content_length),
&key,
ChecksumMode::Verify(&expected_checksums),
ChecksumMode::Verify(expected_checksums),
)
.await?;
+4 -3
View File
@@ -48,8 +48,8 @@ pub(crate) struct SaveStreamResult {
pub(crate) etag: String,
}
pub(crate) enum ChecksumMode<'a> {
Verify(&'a ExpectedChecksums),
pub(crate) enum ChecksumMode {
Verify(ExpectedChecksums),
VerifyFrom {
checksummer: StreamingChecksumReceiver,
trailer_algo: Option<ChecksumAlgorithm>,
@@ -83,6 +83,7 @@ pub async fn handle_put(
let meta = ObjectVersionMetaInner {
headers,
checksum: expected_checksums.extra,
checksum_type: expected_checksums.extra.map(|_| ChecksumType::FullObject),
};
// Determine whether object should be encrypted, and if so the key
@@ -139,7 +140,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
encryption: EncryptionParams,
body: S,
key: &String,
checksum_mode: ChecksumMode<'_>,
checksum_mode: ChecksumMode,
) -> Result<SaveStreamResult, Error> {
let ReqCtx {
garage, bucket_id, ..
+5
View File
@@ -141,6 +141,8 @@ pub struct CompleteMultipartUploadResult {
pub checksum_sha1: Option<Value>,
#[serde(rename = "ChecksumSHA256")]
pub checksum_sha256: Option<Value>,
#[serde(rename = "ChecksumType")]
pub checksum_type: Option<Value>,
}
#[derive(Debug, Serialize, PartialEq, Eq)]
@@ -514,6 +516,7 @@ mod tests {
#[test]
fn complete_multipart_upload_result() -> Result<(), ApiError> {
use garage_api_common::signature::checksum::COMPOSITE;
let result = CompleteMultipartUploadResult {
xmlns: (),
location: Some(Value("https://garage.tld/mybucket/a/plop".to_string())),
@@ -525,6 +528,7 @@ mod tests {
checksum_crc64nvme: None,
checksum_sha1: Some(Value("ZJAnHyG8PeKz9tI8UTcHrJos39A=".into())),
checksum_sha256: None,
checksum_type: Some(Value(COMPOSITE.into())),
};
assert_eq!(
to_xml_with_header(&result)?,
@@ -535,6 +539,7 @@ mod tests {
<Key>a/plop</Key>\
<ETag>&quot;3858f62230ac3c915f300c664312c11f-9&quot;</ETag>\
<ChecksumSHA1>ZJAnHyG8PeKz9tI8UTcHrJos39A=</ChecksumSHA1>\
<ChecksumType>COMPOSITE</ChecksumType>\
</CompleteMultipartUploadResult>"
);
Ok(())