memberlist_proto/
checksum.rs

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/// An error type for parsing checksum algorithm from str.
10#[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/// Checksum error
78#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
79pub enum ChecksumError {
80  /// The checksum algorithm is supported, but the required feature is disabled
81  #[error("the {algo} is supported but the feature {feature} is disabled")]
82  Disabled {
83    /// The algorithm want to use
84    algo: ChecksumAlgorithm,
85    /// The feature that is disabled
86    feature: &'static str,
87  },
88  /// Unknown checksum algorithm
89  #[error("unknown checksum algorithm: {0}")]
90  UnknownAlgorithm(ChecksumAlgorithm),
91  /// Checksum mismatch
92  #[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/// The algorithm used to checksum the message.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::IsVariant)]
112#[non_exhaustive]
113pub enum ChecksumAlgorithm {
114  /// CRC32 IEEE
115  #[display("crc32")]
116  #[cfg(feature = "crc32")]
117  #[cfg_attr(docsrs, doc(cfg(feature = "crc32")))]
118  Crc32,
119  /// XXHash32
120  #[display("xxhash32")]
121  #[cfg(feature = "xxhash32")]
122  #[cfg_attr(docsrs, doc(cfg(feature = "xxhash32")))]
123  XxHash32,
124  /// XXHash64
125  #[display("xxhash64")]
126  #[cfg(feature = "xxhash64")]
127  #[cfg_attr(docsrs, doc(cfg(feature = "xxhash64")))]
128  XxHash64,
129  /// XXHash3
130  #[display("xxhash3")]
131  #[cfg(feature = "xxhash3")]
132  #[cfg_attr(docsrs, doc(cfg(feature = "xxhash3")))]
133  XxHash3,
134  /// Murmur3
135  #[display("murmur3")]
136  #[cfg(feature = "murmur3")]
137  #[cfg_attr(docsrs, doc(cfg(feature = "murmur3")))]
138  Murmur3,
139  /// Unknwon checksum algorithm
140  #[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  /// Returns the checksum algorithm as a `u8`.
240  #[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  /// Returns the checksum algorithm as a `&'static str`.
258  #[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  /// Returns the output size of the checksum algorithm.
277  #[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  /// Calculate the checksum of the data using the specified algorithm.
321  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}