1use std::{borrow::Cow, str::FromStr};
2
3const CRC32_TAG: u8 = 1;
4const XXHASH32_TAG: u8 = 2;
5const XXHASH64_TAG: u8 = 3;
6const XXHASH3_TAG: u8 = 4;
7const MURMUR3_TAG: u8 = 5;
8
9#[derive(Debug, thiserror::Error)]
11#[error("unknown checksum algorithm {0}")]
12pub struct ParseChecksumAlgorithmError(String);
13
14impl FromStr for ChecksumAlgorithm {
15 type Err = ParseChecksumAlgorithmError;
16
17 fn from_str(s: &str) -> Result<Self, Self::Err> {
18 Ok(match s {
19 "crc32" | "Crc32" | "CRC32" => {
20 #[cfg(not(feature = "crc32"))]
21 return Err(ParseChecksumAlgorithmError(
22 "feature `crc32` is disabled".to_string(),
23 ));
24
25 #[cfg(feature = "crc32")]
26 Self::Crc32
27 }
28 "xxhash32" | "XxHash32" | "XXHASH32" | "xxh32" => {
29 #[cfg(not(feature = "xxhash32"))]
30 return Err(ParseChecksumAlgorithmError(
31 "feature `xxhash32` is disabled".to_string(),
32 ));
33
34 #[cfg(feature = "xxhash32")]
35 Self::XxHash32
36 }
37 "xxhash64" | "XxHash64" | "XXHASH64" | "xxh64" => {
38 #[cfg(not(feature = "xxhash64"))]
39 return Err(ParseChecksumAlgorithmError(
40 "feature `xxhash64` is disabled".to_string(),
41 ));
42
43 #[cfg(feature = "xxhash64")]
44 Self::XxHash64
45 }
46 "xxhash3" | "XxHash3" | "XXHASH3" | "xxh3" => {
47 #[cfg(not(feature = "xxhash3"))]
48 return Err(ParseChecksumAlgorithmError(
49 "feature `xxhash3` is disabled".to_string(),
50 ));
51
52 #[cfg(feature = "xxhash3")]
53 Self::XxHash3
54 }
55 "murmur3" | "Murmur3" | "MURMUR3" | "MurMur3" => {
56 #[cfg(not(feature = "murmur3"))]
57 return Err(ParseChecksumAlgorithmError(
58 "feature `murmur3` is disabled".to_string(),
59 ));
60
61 #[cfg(feature = "murmur3")]
62 Self::Murmur3
63 }
64 val if val.starts_with("unknown") => {
65 let val = val.trim_start_matches("unknown(").trim_end_matches(')');
66 Self::Unknown(
67 val
68 .parse::<u8>()
69 .map_err(|_| ParseChecksumAlgorithmError(val.to_string()))?,
70 )
71 }
72 val => return Err(ParseChecksumAlgorithmError(val.to_string())),
73 })
74 }
75}
76
77#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
79pub enum ChecksumError {
80 #[error("the {algo} is supported but the feature {feature} is disabled")]
82 Disabled {
83 algo: ChecksumAlgorithm,
85 feature: &'static str,
87 },
88 #[error("unknown checksum algorithm: {0}")]
90 UnknownAlgorithm(ChecksumAlgorithm),
91 #[error("checksum mismatch")]
93 Mismatch,
94}
95
96impl ChecksumError {
97 #[cfg(not(all(
98 feature = "crc32",
99 feature = "xxhash32",
100 feature = "xxhash64",
101 feature = "xxhash3",
102 feature = "murmur3"
103 )))]
104 #[inline]
105 pub(crate) const fn disabled(algo: ChecksumAlgorithm, feature: &'static str) -> Self {
106 Self::Disabled { algo, feature }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::IsVariant)]
112#[non_exhaustive]
113pub enum ChecksumAlgorithm {
114 #[display("crc32")]
116 #[cfg(feature = "crc32")]
117 #[cfg_attr(docsrs, doc(cfg(feature = "crc32")))]
118 Crc32,
119 #[display("xxhash32")]
121 #[cfg(feature = "xxhash32")]
122 #[cfg_attr(docsrs, doc(cfg(feature = "xxhash32")))]
123 XxHash32,
124 #[display("xxhash64")]
126 #[cfg(feature = "xxhash64")]
127 #[cfg_attr(docsrs, doc(cfg(feature = "xxhash64")))]
128 XxHash64,
129 #[display("xxhash3")]
131 #[cfg(feature = "xxhash3")]
132 #[cfg_attr(docsrs, doc(cfg(feature = "xxhash3")))]
133 XxHash3,
134 #[display("murmur3")]
136 #[cfg(feature = "murmur3")]
137 #[cfg_attr(docsrs, doc(cfg(feature = "murmur3")))]
138 Murmur3,
139 #[display("unknown({_0})")]
141 Unknown(u8),
142}
143
144impl ChecksumAlgorithm {
145 #[inline]
146 pub(crate) const fn unknown_or_disabled(&self) -> Option<ChecksumError> {
147 match *self {
148 #[cfg(feature = "crc32")]
149 Self::Crc32 => None,
150 #[cfg(feature = "xxhash32")]
151 Self::XxHash32 => None,
152 #[cfg(feature = "xxhash64")]
153 Self::XxHash64 => None,
154 #[cfg(feature = "xxhash3")]
155 Self::XxHash3 => None,
156 #[cfg(feature = "murmur3")]
157 Self::Murmur3 => None,
158 Self::Unknown(val) => Some({
159 #[cfg(not(all(
160 feature = "crc32",
161 feature = "xxhash32",
162 feature = "xxhash64",
163 feature = "xxhash3",
164 feature = "murmur3"
165 )))]
166 match val {
167 CRC32_TAG => ChecksumError::disabled(Self::Unknown(val), "crc32"),
168 XXHASH32_TAG => ChecksumError::disabled(Self::Unknown(val), "xxhash32"),
169 XXHASH64_TAG => ChecksumError::disabled(Self::Unknown(val), "xxhash64"),
170 XXHASH3_TAG => ChecksumError::disabled(Self::Unknown(val), "xxhash3"),
171 MURMUR3_TAG => ChecksumError::disabled(Self::Unknown(val), "murmur3"),
172 _ => ChecksumError::UnknownAlgorithm(Self::Unknown(val)),
173 }
174
175 #[cfg(all(
176 feature = "crc32",
177 feature = "xxhash32",
178 feature = "xxhash64",
179 feature = "xxhash3",
180 feature = "murmur3"
181 ))]
182 ChecksumError::UnknownAlgorithm(Self::Unknown(val))
183 }),
184 }
185 }
186}
187
188#[allow(clippy::derivable_impls)]
189impl Default for ChecksumAlgorithm {
190 fn default() -> Self {
191 cfg_if::cfg_if! {
192 if #[cfg(feature = "crc32")] {
193 Self::Crc32
194 } else if #[cfg(feature = "xxhash32")] {
195 Self::XxHash32
196 } else if #[cfg(feature = "xxhash64")] {
197 Self::XxHash64
198 } else if #[cfg(feature = "xxhash3")] {
199 Self::XxHash3
200 } else if #[cfg(feature = "murmur3")] {
201 Self::Murmur3
202 } else {
203 Self::Unknown(255)
204 }
205 }
206 }
207}
208
209#[cfg(any(feature = "quickcheck", test))]
210const _: () = {
211 use quickcheck::Arbitrary;
212
213 impl ChecksumAlgorithm {
214 const MAX: Self = Self::Murmur3;
215 const MIN: Self = Self::Crc32;
216 }
217
218 impl Arbitrary for ChecksumAlgorithm {
219 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
220 let val = (u8::arbitrary(g) % Self::MAX.as_u8()) + Self::MIN.as_u8();
221 match val {
222 #[cfg(feature = "crc32")]
223 CRC32_TAG => Self::Crc32,
224 #[cfg(feature = "xxhash32")]
225 XXHASH32_TAG => Self::XxHash32,
226 #[cfg(feature = "xxhash64")]
227 XXHASH64_TAG => Self::XxHash64,
228 #[cfg(feature = "xxhash3")]
229 XXHASH3_TAG => Self::XxHash3,
230 #[cfg(feature = "murmur3")]
231 MURMUR3_TAG => Self::Murmur3,
232 _ => Self::Unknown(u8::MAX),
233 }
234 }
235 }
236};
237
238impl ChecksumAlgorithm {
239 #[inline]
241 pub const fn as_u8(&self) -> u8 {
242 match self {
243 #[cfg(feature = "crc32")]
244 Self::Crc32 => CRC32_TAG,
245 #[cfg(feature = "xxhash32")]
246 Self::XxHash32 => XXHASH32_TAG,
247 #[cfg(feature = "xxhash64")]
248 Self::XxHash64 => XXHASH64_TAG,
249 #[cfg(feature = "xxhash3")]
250 Self::XxHash3 => XXHASH3_TAG,
251 #[cfg(feature = "murmur3")]
252 Self::Murmur3 => MURMUR3_TAG,
253 Self::Unknown(v) => *v,
254 }
255 }
256
257 #[inline]
259 pub fn as_str(&self) -> Cow<'static, str> {
260 let val = match self {
261 #[cfg(feature = "crc32")]
262 Self::Crc32 => "crc32",
263 #[cfg(feature = "xxhash32")]
264 Self::XxHash32 => "xxhash32",
265 #[cfg(feature = "xxhash64")]
266 Self::XxHash64 => "xxhash64",
267 #[cfg(feature = "xxhash3")]
268 Self::XxHash3 => "xxhash3",
269 #[cfg(feature = "murmur3")]
270 Self::Murmur3 => "murmur3",
271 Self::Unknown(e) => return Cow::Owned(format!("unknown({})", e)),
272 };
273 Cow::Borrowed(val)
274 }
275
276 #[inline]
278 pub const fn output_size(&self) -> usize {
279 match self {
280 #[cfg(feature = "crc32")]
281 Self::Crc32 => 4,
282 #[cfg(feature = "xxhash32")]
283 Self::XxHash32 => 4,
284 #[cfg(feature = "xxhash64")]
285 Self::XxHash64 => 8,
286 #[cfg(feature = "xxhash3")]
287 Self::XxHash3 => 8,
288 #[cfg(feature = "murmur3")]
289 Self::Murmur3 => 4,
290 Self::Unknown(_) => 0,
291 }
292 }
293}
294
295impl From<u8> for ChecksumAlgorithm {
296 fn from(value: u8) -> Self {
297 match value {
298 #[cfg(feature = "crc32")]
299 CRC32_TAG => Self::Crc32,
300 #[cfg(feature = "xxhash32")]
301 XXHASH32_TAG => Self::XxHash32,
302 #[cfg(feature = "xxhash64")]
303 XXHASH64_TAG => Self::XxHash64,
304 #[cfg(feature = "xxhash3")]
305 XXHASH3_TAG => Self::XxHash3,
306 #[cfg(feature = "murmur3")]
307 MURMUR3_TAG => Self::Murmur3,
308 _ => Self::Unknown(value),
309 }
310 }
311}
312
313impl From<ChecksumAlgorithm> for u8 {
314 fn from(value: ChecksumAlgorithm) -> Self {
315 value.as_u8()
316 }
317}
318
319impl ChecksumAlgorithm {
320 pub fn checksum(&self, data: &[u8]) -> Result<u64, ChecksumError> {
322 Ok(match self {
323 #[cfg(feature = "crc32")]
324 Self::Crc32 => crc32fast::hash(data) as u64,
325 #[cfg(feature = "xxhash32")]
326 Self::XxHash32 => xxhash_rust::xxh32::xxh32(data, 0) as u64,
327 #[cfg(feature = "xxhash64")]
328 Self::XxHash64 => xxhash_rust::xxh64::xxh64(data, 0),
329 #[cfg(feature = "xxhash3")]
330 Self::XxHash3 => xxhash_rust::xxh3::xxh3_64(data),
331 #[cfg(feature = "murmur3")]
332 Self::Murmur3 => {
333 use core::hash::Hasher as _;
334
335 let mut hasher = hash32::Murmur3Hasher::default();
336 hasher.write(data);
337 hasher.finish()
338 }
339 algo => return Err(ChecksumError::UnknownAlgorithm(*algo)),
340 })
341 }
342}
343
344#[cfg(feature = "serde")]
345const _: () = {
346 use serde::{Deserialize, Deserializer, Serialize, Serializer};
347
348 impl Serialize for ChecksumAlgorithm {
349 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
350 where
351 S: Serializer,
352 {
353 if serializer.is_human_readable() {
354 serializer.serialize_str(self.as_str().as_ref())
355 } else {
356 serializer.serialize_u8(self.as_u8())
357 }
358 }
359 }
360
361 impl<'de> Deserialize<'de> for ChecksumAlgorithm {
362 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
363 where
364 D: Deserializer<'de>,
365 {
366 if deserializer.is_human_readable() {
367 <&str>::deserialize(deserializer).and_then(|s| {
368 s.parse::<ChecksumAlgorithm>()
369 .map_err(serde::de::Error::custom)
370 })
371 } else {
372 let v = u8::deserialize(deserializer)?;
373 Ok(Self::from(v))
374 }
375 }
376 }
377};
378
379#[test]
380fn test_checksum_algorithm_from_str() {
381 #[cfg(feature = "crc32")]
382 assert_eq!(
383 "crc32".parse::<ChecksumAlgorithm>().unwrap(),
384 ChecksumAlgorithm::Crc32
385 );
386 #[cfg(feature = "xxhash32")]
387 assert_eq!(
388 "xxhash32".parse::<ChecksumAlgorithm>().unwrap(),
389 ChecksumAlgorithm::XxHash32
390 );
391 #[cfg(feature = "xxhash64")]
392 assert_eq!(
393 "xxhash64".parse::<ChecksumAlgorithm>().unwrap(),
394 ChecksumAlgorithm::XxHash64
395 );
396 #[cfg(feature = "xxhash3")]
397 assert_eq!(
398 "xxhash3".parse::<ChecksumAlgorithm>().unwrap(),
399 ChecksumAlgorithm::XxHash3
400 );
401 #[cfg(feature = "murmur3")]
402 assert_eq!(
403 "murmur3".parse::<ChecksumAlgorithm>().unwrap(),
404 ChecksumAlgorithm::Murmur3
405 );
406 assert_eq!(
407 "unknown(33)".parse::<ChecksumAlgorithm>().unwrap(),
408 ChecksumAlgorithm::Unknown(33)
409 );
410 assert!("unknown".parse::<ChecksumAlgorithm>().is_err());
411}
412
413#[cfg(all(test, feature = "serde"))]
414#[quickcheck_macros::quickcheck]
415fn checksum_algorithm_serde(algo: ChecksumAlgorithm) -> bool {
416 use bincode::config::standard;
417
418 let Ok(serialized) = serde_json::to_string(&algo) else {
419 return false;
420 };
421 let Ok(deserialized) = serde_json::from_str(&serialized) else {
422 return false;
423 };
424 if algo != deserialized {
425 return false;
426 }
427
428 let Ok(serialized) = bincode::serde::encode_to_vec(algo, standard()) else {
429 return false;
430 };
431
432 let Ok((deserialized, _)) = bincode::serde::decode_from_slice(&serialized, standard()) else {
433 return false;
434 };
435
436 algo == deserialized
437}