api: switch to crc_fast for checksumming and implement full-object mpu checksums

This commit is contained in:
Alex Auvolat
2025-05-09 16:32:33 +02:00
parent 589a992af8
commit e475c7f802
9 changed files with 235 additions and 149 deletions
Generated
+61 -22
View File
@@ -556,7 +556,7 @@ checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
dependencies = [
"getrandom 0.2.15",
"instant",
"rand",
"rand 0.8.5",
]
[[package]]
@@ -838,6 +838,20 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc-fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0ab672ed7761a44410115d2e0e5ccd86b987ba186a78a4148fb02bb302a774"
dependencies = [
"cc",
"crc",
"digest",
"libc",
"rand 0.9.1",
"regex",
]
[[package]]
name = "crc32c"
version = "0.6.8"
@@ -896,7 +910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"rand_core 0.6.4",
"typenum",
]
@@ -1211,7 +1225,7 @@ dependencies = [
"bytes",
"bytesize",
"chrono",
"crc32fast",
"crc-fast",
"format_table",
"futures",
"garage_api_admin",
@@ -1292,9 +1306,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"chrono",
"crc32c",
"crc32fast",
"crc64fast-nvme",
"crc-fast",
"crypto-common",
"err-derive",
"futures",
@@ -1353,9 +1365,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"chrono",
"crc32c",
"crc32fast",
"crc64fast-nvme",
"crc-fast",
"err-derive",
"form_urlencoded",
"futures",
@@ -1407,7 +1417,7 @@ dependencies = [
"garage_util",
"hex",
"opentelemetry",
"rand",
"rand 0.8.5",
"serde",
"tokio",
"tokio-util 0.7.14",
@@ -1448,7 +1458,7 @@ dependencies = [
"hex",
"http 1.3.1",
"parse_duration",
"rand",
"rand 0.8.5",
"serde",
"serde_bytes",
"tokio",
@@ -1473,7 +1483,7 @@ dependencies = [
"opentelemetry-contrib",
"pin-project",
"pretty_env_logger",
"rand",
"rand 0.8.5",
"rmp-serde",
"serde",
"tokio",
@@ -1503,7 +1513,7 @@ dependencies = [
"nix",
"opentelemetry",
"pnet_datalink",
"rand",
"rand 0.8.5",
"reqwest",
"schemars",
"serde",
@@ -1527,7 +1537,7 @@ dependencies = [
"hex",
"hexdump",
"opentelemetry",
"rand",
"rand 0.8.5",
"serde",
"serde_bytes",
"tokio",
@@ -1554,7 +1564,7 @@ dependencies = [
"lazy_static",
"mktemp",
"opentelemetry",
"rand",
"rand 0.8.5",
"rmp-serde",
"rustc_version",
"serde",
@@ -2868,7 +2878,7 @@ dependencies = [
"lazy_static",
"percent-encoding",
"pin-project",
"rand",
"rand 0.8.5",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
@@ -3010,7 +3020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"rand_core 0.6.4",
"subtle",
]
@@ -3359,8 +3369,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
@@ -3370,7 +3390,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
@@ -3382,6 +3412,15 @@ dependencies = [
"getrandom 0.2.15",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -4465,7 +4504,7 @@ dependencies = [
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util 0.7.14",
@@ -4717,7 +4756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom 0.2.15",
"rand",
"rand 0.8.5",
]
[[package]]
+1 -3
View File
@@ -49,9 +49,7 @@ bytes = "1.0"
bytesize = "1.1"
cfg-if = "1.0"
chrono = { version = "0.4", features = ["serde"] }
crc32fast = "1.4"
crc32c = "0.6"
crc64fast-nvme = "1.2"
crc-fast = "1.1"
crypto-common = "0.1"
err-derive = "0.3"
gethostname = "0.4"
+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
+35 -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;
@@ -41,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)
@@ -52,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>,
@@ -103,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());
@@ -119,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());
@@ -143,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);
@@ -161,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()),
@@ -197,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
+2
View File
@@ -1002,9 +1002,11 @@ mod tests {
inner: ObjectVersionMetaInner {
headers: vec![],
checksum: None,
checksum_type: None,
},
},
checksum_algorithm: None,
checksum_type: None,
},
}
}
+126 -85
View File
@@ -1,12 +1,9 @@
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::{header::HeaderValue, HeaderMap, Request, Response};
use md5::{Digest, Md5};
@@ -410,7 +407,11 @@ pub async fn handle_complete_multipart_upload(
// https://teppen.io/2018/06/23/aws_s3_etags/
let mut checksummer = MultipartChecksummer::init(checksum_algorithm, checksum_type);
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();
@@ -693,36 +694,14 @@ pub(crate) struct MultipartChecksummer {
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>, cktype: Option<ChecksumType>) -> Self {
if cktype == Some(ChecksumType::FullObject) {
todo!()
}
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: match (algo, cktype) {
(None, None) => None,
(Some(algo), Some(cktype)) => Some(MultipartExtraChecksummer::init(algo, cktype)),
_ => unreachable!(),
},
}
}
@@ -731,68 +710,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())
}
}
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ static_init.workspace = true
assert-json-diff.workspace = true
serde_json.workspace = true
base64.workspace = true
crc32fast.workspace = true
crc-fast.workspace = true
k2v-client.workspace = true
+7 -8
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use base64::prelude::*;
use crc32fast::Hasher as Crc32;
use crc_fast::{checksum as crc_checksum, CrcAlgorithm};
use crate::common;
use crate::common::ext::CommandExt;
@@ -69,9 +69,8 @@ async fn test_putobject_streaming() {
{
let etag = "\"46cf18a9b447991b450cad3facf5937e\"";
let mut crc32 = Crc32::new();
crc32.update(&BODY[..]);
let crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(crc32.finalize())[..]);
let crc32 = crc_checksum(CrcAlgorithm::Crc32IsoHdlc, &BODY[..]) as u32;
let crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(crc32)[..]);
let mut headers = HashMap::new();
headers.insert("x-amz-checksum-crc32".to_owned(), crc32.clone());
@@ -129,7 +128,8 @@ async fn test_putobject_streaming_unsigned_trailer() {
let mut headers = HashMap::new();
headers.insert("content-type".to_owned(), content_type.to_owned());
let empty_crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(Crc32::new().finalize())[..]);
let empty_crc32 = crc_checksum(CrcAlgorithm::Crc32IsoHdlc, &[]) as u32;
let empty_crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(empty_crc32)[..]);
let res = ctx
.custom_request
@@ -180,9 +180,8 @@ async fn test_putobject_streaming_unsigned_trailer() {
{
let etag = "\"46cf18a9b447991b450cad3facf5937e\"";
let mut crc32 = Crc32::new();
crc32.update(&BODY[..]);
let crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(crc32.finalize())[..]);
let crc32 = crc_checksum(CrcAlgorithm::Crc32IsoHdlc, &BODY[..]) as u32;
let crc32 = BASE64_STANDARD.encode(&u32::to_be_bytes(crc32)[..]);
// try sending with wrong crc32, check that it fails
let err_res = ctx