tlsh/
hash.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// SPDX-FileCopyrightText: Copyright 2013 Trend Micro Incorporated
3// SPDX-FileCopyrightText: Copyright (C) 2024 Tsukasa OI <floss_ssdeep@irq.a4lg.com>.
4
5//! The fuzzy hash and its parts (unless a part has its own module).
6
7use core::fmt::Display;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::de::Visitor;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::compare::ComparisonConfiguration;
16use crate::errors::{OperationError, ParseError};
17use crate::hash::body::FuzzyHashBody;
18use crate::hash::checksum::FuzzyHashChecksum;
19use crate::hash::qratios::FuzzyHashQRatios;
20use crate::length::FuzzyHashLengthEncoding;
21use crate::params::{ConstrainedFuzzyHashParams, FuzzyHashParams};
22use crate::FuzzyHashType;
23
24pub mod body;
25pub mod checksum;
26pub mod qratios;
27
28/// Denotes the prefix on the TLSH's hexadecimal representation.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub enum HexStringPrefix {
31    /// Raw / empty.
32    ///
33    /// If this value is specified to convert a fuzzy hash to string, no prefix
34    /// is added and the result will purely consist of hexadecimal digits.
35    ///
36    /// If this value is specified to a parser method, it expects that the
37    /// string immediately starts with a hexadecimal digit.
38    Empty,
39
40    /// With TLSH prefix and version (current default).
41    ///
42    /// This crate only supports version 1 prefix (`"T1"`).
43    ///
44    /// If this value is specified to convert a fuzzy hash to string, the TLSHv1
45    /// prefix (`"T1"`) is added before the hexadecimal digits.
46    ///
47    /// If this value is specified to a parser method, it expects that the
48    /// TLSHv1 prefix (`"T1"`) exists at the beginning.
49    /// *This prefix is case-sensitive.*
50    #[default]
51    WithVersion,
52}
53
54/// The public part for later `pub use` at crate root.
55pub(crate) mod public {
56    use super::*;
57
58    /// The trait to represent a fuzzy hash (TLSH).
59    ///
60    /// # TLSH Internals
61    ///
62    /// A fuzzy hash (TLSH) is composed of up to four parts:
63    ///
64    /// 1.  Checksum (checksum of the input, 1 or 3 bytes)
65    ///     *   Trait: [`crate::hash::checksum::FuzzyHashChecksum`]
66    ///     *   Internal Type: [`crate::hash::checksum::FuzzyHashChecksumData`]
67    /// 2.  Data Length (approximated, encoded as an 8-bit integer)
68    ///     *   Internal Type: [`crate::length::FuzzyHashLengthEncoding`]
69    /// 3.  Q ratio pair, each Q ratio value reflecting the statistic distribution.
70    ///     *   Internal Type: [`crate::hash::qratios::FuzzyHashQRatios`]
71    /// 4.  Body.  Encoded as the specific number of quartile values
72    ///     (each in 2-bits), in which the quartile count equals the number
73    ///     of "buckets", used to gather statistic information (local features)
74    ///     of the given input.
75    ///     *   Trait: [`crate::hash::body::FuzzyHashBody`]
76    ///     *   Internal Type: [`crate::hash::body::FuzzyHashBodyData`]
77    ///
78    /// Note that the checksum part can be always zero on some TLSH
79    /// configurations (i.e. multi-threading is enabled or private flag is set).
80    ///
81    /// This trait is implemented by [`FuzzyHash`].
82    pub trait FuzzyHashType: Sized + FromStr<Err = ParseError> + Display {
83        /// The type of the checksum part.
84        ///
85        /// This is an instantiation of
86        /// [`crate::hash::checksum::FuzzyHashChecksumData`].
87        type ChecksumType: FuzzyHashChecksum;
88
89        /// The type of the body part.
90        ///
91        /// This is an instantiation of
92        /// [`crate::hash::body::FuzzyHashBodyData`].
93        type BodyType: FuzzyHashBody;
94
95        /// Number of the buckets.
96        ///
97        /// Specifically, this constant denotes the number of *effective*
98        /// buckets that are used to construct a fuzzy hash.
99        ///
100        /// Sometimes, the number of physical buckets (number of possible
101        /// results after [the Pearson hashing](crate::pearson) or its variant)
102        /// differs from the number of effective buckets.
103        ///
104        /// | Variant | Effective Buckets | Physical Buckets |
105        /// | ------- | -----------------:| ----------------:|
106        /// | Short   |              `48` |            *`49` |
107        /// | Normal  |             `128` |           *`256` |
108        /// | Long    |             `256` |            `256` |
109        ///
110        /// On those cases, only the first effective buckets are used and the
111        /// rest are ignored / dropped.
112        const NUMBER_OF_BUCKETS: usize;
113
114        /// Total size of the fuzzy hash (if represented as a byte array)
115        /// in bytes (in the binary representation).
116        ///
117        /// This is the fixed size and required buffer size for the
118        /// [`store_into_bytes()`](Self::store_into_bytes()) method.
119        const SIZE_IN_BYTES: usize;
120
121        /// Length in the hexadecimal string representation
122        /// (except the prefix `"T1"`).
123        ///
124        /// This is always [`LEN_IN_STR`](Self::LEN_IN_STR) minus 2.
125        ///
126        /// This is the fixed size and required buffer size for the
127        /// [`store_into_str_bytes()`](Self::store_into_str_bytes()) method with
128        /// `prefix` of [`HexStringPrefix::Empty`].
129        const LEN_IN_STR_EXCEPT_PREFIX: usize;
130
131        /// Length in the hexadecimal string representation.
132        ///
133        /// This is always
134        /// [`LEN_IN_STR_EXCEPT_PREFIX`](Self::LEN_IN_STR_EXCEPT_PREFIX) plus 2.
135        ///
136        /// This is the fixed size and required buffer size for the
137        /// [`store_into_str_bytes()`](Self::store_into_str_bytes()) method with
138        /// `prefix` of [`HexStringPrefix::WithVersion`].
139        const LEN_IN_STR: usize;
140
141        /// Returns the checksum part.
142        fn checksum(&self) -> &Self::ChecksumType;
143        /// Returns the length part.
144        fn length(&self) -> &FuzzyHashLengthEncoding;
145        /// Returns the Q ratio pair part.
146        fn qratios(&self) -> &FuzzyHashQRatios;
147        /// Returns the body part.
148        fn body(&self) -> &Self::BodyType;
149
150        /// Try parsing the fuzzy hash object from the given TLSH's hexadecimal
151        /// representation and the operation mode.
152        ///
153        /// If the argument `prefix` is [`None`], the existence of the prefix
154        /// will be auto-detected.  Otherwise, the existence of
155        /// [the specified prefix](HexStringPrefix) is checked.
156        fn from_str_bytes(
157            bytes: &[u8],
158            prefix: Option<HexStringPrefix>,
159        ) -> Result<Self, ParseError>;
160
161        /// Try parsing the fuzzy hash object from the given TLSH's hexadecimal
162        /// representation and the operation mode.
163        ///
164        /// If the argument `prefix` is [`None`], the existence of the prefix
165        /// will be auto-detected.  Otherwise, the existence of
166        /// [the specified prefix](HexStringPrefix) is checked.
167        #[inline(always)]
168        fn from_str_with(s: &str, prefix: Option<HexStringPrefix>) -> Result<Self, ParseError> {
169            Self::from_str_bytes(s.as_bytes(), prefix)
170        }
171
172        /// Store the contents of this object to the specified slice
173        /// (in a binary format).
174        ///
175        /// This method stores the contents as a binary format suitable
176        /// for serialization and parsing, to the specified slice.
177        ///
178        /// # The Binary Format with a Warning
179        ///
180        /// The binary format **slightly differs** from the representation you
181        /// might expect from the TLSH's hexadecimal representation.
182        ///
183        /// The TLSH's hexadecimal representation has weird nibble endianness on
184        /// the header (checksum, length and Q ratio pair parts).  For instance,
185        /// the checksum part in the TLSH's hex representation `"42"` means
186        /// the real checksum value of `0x24`.
187        ///
188        /// The body part is also *reversed* in a sense but this part is handled
189        /// equivalently by this crate (because only "byte" ordering is
190        /// reversed in one interpretation).  So, you may get the one you
191        /// may expect from the TLSH's hexadecimal representation,
192        /// at least in the body.
193        ///
194        /// The binary format used by this method doesn't do that conversion
195        /// on the header.
196        ///
197        /// For instance, following TLSH hash
198        /// ([normal 128 buckets with long 3-byte checksum](crate::hashes::NormalWithLongChecksum)):
199        ///
200        /// ```text
201        /// T170F37CF0DC36520C1B007FD320B9B266559FD998A0200725E75AFCEAC99F5881184A4B1AA2 (raw)
202        ///
203        /// T1 70F37C F0 DC 36520C1B007FD320B9B266559FD998A0200725E75AFCEAC99F5881184A4B1AA2 (decomposed)
204        /// |  |      |  |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
205        /// |  |      |  |                Body / Buckets in quartiles (32-byte, 128 buckets)
206        /// |  |      |  +- Q ratio pair (reversed; Q1 ratio -> Q2 ratio)
207        /// |  |      +- Length (reversed)
208        /// |  +- 3-byte Checksum (reversed per byte; AB CD EF -> BA DC FE)
209        /// +- Header and version
210        /// ```
211        ///
212        /// will be written as the following byte sequence by this method:
213        ///
214        /// ```text
215        /// 073FC70FCD36520C1B007FD320B9B266559FD998A0200725E75AFCEAC99F5881184A4B1AA2 (raw)
216        ///
217        /// __ 073FC7 0F CD 36520C1B007FD320B9B266559FD998A0200725E75AFCEAC99F5881184A4B1AA2 (decomposed)
218        /// |  |      |  |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
219        /// |  |      |  |                Body / Buckets in quartiles (32-byte, 128 buckets)
220        /// |  |      |  +- Q ratio pair (Q2 ratio -> Q1 ratio)                 (kept as is)
221        /// |  |      +- Length
222        /// |  +- 3-byte Checksum
223        /// +- No header and version
224        /// ```
225        ///
226        /// # The Specification
227        ///
228        /// This method concatenates:
229        ///
230        /// 1.  Checksum
231        /// 2.  Length encoding
232        /// 3.  Q ratio pair
233        /// 4.  Body
234        ///
235        /// in that order (without any explicit variable length encodings or
236        /// separators).  Each binary representation of the part can be
237        /// retrieved as either an [`u8`] value or a slice / array of [`u8`].
238        ///
239        /// See [struct documentation](Self#tlsh-internals) for details.
240        fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, OperationError>;
241
242        /// Store the contents of this object to the specified slice
243        /// (in the TLSH's hexadecimal representation).
244        ///
245        /// This method stores the contents as a TLSH's hexadecimal string
246        /// representation with [the specified prefix](HexStringPrefix).
247        fn store_into_str_bytes(
248            &self,
249            out: &mut [u8],
250            prefix: HexStringPrefix,
251        ) -> Result<usize, OperationError>;
252
253        /// Compute the max distance on [comparison](Self::compare()) with
254        /// the specified comparison configuration.
255        ///
256        /// If you need the maximum distance on the default configuration,
257        /// use the first argument of [`Default::default()`].
258        fn max_distance(config: ComparisonConfiguration) -> u32;
259
260        /// Compare with another instance (with a configuration) and
261        /// return the distance between them.
262        ///
263        /// Normally, you will likely use the default configuration and use
264        /// [`compare()`](Self::compare()) instead.
265        fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32;
266
267        /// Compare with another instance with [the default configuration](ComparisonConfiguration::Default)
268        /// and return the distance between them.
269        ///
270        /// If you need to use a non-default option, use
271        /// [`compare_with_config()`](Self::compare_with_config()) instead.
272        #[inline(always)]
273        fn compare(&self, other: &Self) -> u32 {
274            self.compare_with_config(other, ComparisonConfiguration::Default)
275        }
276    }
277}
278
279/// The inner representation and its implementation.
280pub(crate) mod inner {
281    use super::*;
282
283    use crate::buckets::constrained::{FuzzyHashBucketMapper, FuzzyHashBucketsInfo};
284    use crate::hash::body::FuzzyHashBodyData;
285    use crate::hash::checksum::FuzzyHashChecksumData;
286    use crate::macros::{invariant, optionally_unsafe};
287    use crate::params::{ConstrainedVerboseFuzzyHashParams, VerboseFuzzyHashParams};
288    #[cfg(not(feature = "opt-simd-convert-hex"))]
289    use crate::parse::hex_str::encode_array;
290    use crate::parse::hex_str::{encode_rev_1, encode_rev_array};
291
292    /// The struct representing a fuzzy hash.
293    ///
294    /// This type is used as an inner representation of [`super::FuzzyHash`].
295    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
296    pub struct FuzzyHash<
297        const SIZE_CKSUM: usize,
298        const SIZE_BODY: usize,
299        const SIZE_BUCKETS: usize,
300        const SIZE_IN_BYTES: usize,
301        const SIZE_IN_STR_BYTES: usize,
302    >
303    where
304        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
305        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
306        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
307        VerboseFuzzyHashParams<
308            SIZE_CKSUM,
309            SIZE_BODY,
310            SIZE_BUCKETS,
311            SIZE_IN_BYTES,
312            SIZE_IN_STR_BYTES,
313        >: ConstrainedVerboseFuzzyHashParams,
314    {
315        /// The body part.
316        body: FuzzyHashBodyData<SIZE_BODY>,
317        /// The checksum part.
318        checksum: FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>,
319        /// The encoded data length part.
320        lvalue: FuzzyHashLengthEncoding,
321        /// The Q ratio pair.
322        qratios: FuzzyHashQRatios,
323    }
324
325    impl<
326            const SIZE_CKSUM: usize,
327            const SIZE_BODY: usize,
328            const SIZE_BUCKETS: usize,
329            const SIZE_IN_BYTES: usize,
330            const SIZE_IN_STR_BYTES: usize,
331        > FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
332    where
333        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
334        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
335        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
336        VerboseFuzzyHashParams<
337            SIZE_CKSUM,
338            SIZE_BODY,
339            SIZE_BUCKETS,
340            SIZE_IN_BYTES,
341            SIZE_IN_STR_BYTES,
342        >: ConstrainedVerboseFuzzyHashParams,
343    {
344        /// Creates an object from its raw parts.
345        pub(crate) fn from_raw(
346            body: FuzzyHashBodyData<SIZE_BODY>,
347            checksum: FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>,
348            lvalue: FuzzyHashLengthEncoding,
349            qratios: FuzzyHashQRatios,
350        ) -> Self {
351            Self {
352                body,
353                checksum,
354                lvalue,
355                qratios,
356            }
357        }
358    }
359
360    impl<
361            const SIZE_CKSUM: usize,
362            const SIZE_BODY: usize,
363            const SIZE_BUCKETS: usize,
364            const SIZE_IN_BYTES: usize,
365            const SIZE_IN_STR_BYTES: usize,
366        > FuzzyHashType
367        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
368    where
369        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
370        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
371        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
372        VerboseFuzzyHashParams<
373            SIZE_CKSUM,
374            SIZE_BODY,
375            SIZE_BUCKETS,
376            SIZE_IN_BYTES,
377            SIZE_IN_STR_BYTES,
378        >: ConstrainedVerboseFuzzyHashParams,
379    {
380        type ChecksumType = FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>;
381        type BodyType = FuzzyHashBodyData<SIZE_BODY>;
382
383        const NUMBER_OF_BUCKETS: usize = SIZE_BUCKETS;
384        const SIZE_IN_BYTES: usize = SIZE_IN_BYTES;
385        const LEN_IN_STR_EXCEPT_PREFIX: usize = SIZE_IN_STR_BYTES - 2;
386        const LEN_IN_STR: usize = SIZE_IN_STR_BYTES;
387
388        #[inline]
389        fn from_str_bytes(
390            bytes: &[u8],
391            prefix: Option<HexStringPrefix>,
392        ) -> Result<Self, ParseError> {
393            let mut bytes = bytes;
394            let prefix = match prefix {
395                None => {
396                    if bytes.len() == Self::LEN_IN_STR_EXCEPT_PREFIX {
397                        HexStringPrefix::Empty
398                    } else if bytes.len() == Self::LEN_IN_STR {
399                        HexStringPrefix::WithVersion
400                    } else {
401                        return Err(ParseError::InvalidStringLength);
402                    }
403                }
404                Some(x) => x,
405            };
406            match prefix {
407                HexStringPrefix::Empty => {
408                    if bytes.len() != Self::LEN_IN_STR_EXCEPT_PREFIX {
409                        return Err(ParseError::InvalidStringLength);
410                    }
411                }
412                HexStringPrefix::WithVersion => {
413                    if bytes.len() != Self::LEN_IN_STR {
414                        return Err(ParseError::InvalidStringLength);
415                    }
416                    if &bytes[0..2] != b"T1" {
417                        return Err(ParseError::InvalidPrefix);
418                    }
419                    bytes = &bytes[2..];
420                }
421            }
422            let checksum = FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::from_str_bytes(
423                &bytes[0..SIZE_CKSUM * 2],
424            )?;
425            #[cfg(feature = "strict-parser")]
426            if !checksum.is_valid() {
427                return Err(ParseError::InvalidChecksum);
428            }
429            bytes = &bytes[SIZE_CKSUM * 2..];
430            let lvalue = FuzzyHashLengthEncoding::from_str_bytes(&bytes[0..2])?;
431            #[cfg(feature = "strict-parser")]
432            if !lvalue.is_valid() {
433                return Err(ParseError::LengthIsTooLarge);
434            }
435            let qratios = FuzzyHashQRatios::from_str_bytes(&bytes[2..4])?;
436            let body = FuzzyHashBodyData::<SIZE_BODY>::from_str_bytes(&bytes[4..])?;
437            Ok(Self {
438                body,
439                checksum,
440                lvalue,
441                qratios,
442            })
443        }
444
445        #[inline(always)]
446        fn checksum(&self) -> &Self::ChecksumType {
447            &self.checksum
448        }
449        #[inline(always)]
450        fn length(&self) -> &FuzzyHashLengthEncoding {
451            &self.lvalue
452        }
453        #[inline(always)]
454        fn qratios(&self) -> &FuzzyHashQRatios {
455            &self.qratios
456        }
457        #[inline(always)]
458        fn body(&self) -> &Self::BodyType {
459            &self.body
460        }
461
462        #[inline]
463        fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, crate::errors::OperationError> {
464            if out.len() < Self::SIZE_IN_BYTES {
465                return Err(OperationError::BufferIsTooSmall);
466            }
467            out[0..SIZE_CKSUM].copy_from_slice(self.checksum.data());
468            out[SIZE_CKSUM] = self.lvalue.value();
469            out[SIZE_CKSUM + 1] = self.qratios.value();
470            out[SIZE_CKSUM + 2..SIZE_IN_BYTES].copy_from_slice(self.body.data());
471            Ok(Self::SIZE_IN_BYTES)
472        }
473
474        #[inline]
475        fn store_into_str_bytes(
476            &self,
477            out: &mut [u8],
478            prefix: HexStringPrefix,
479        ) -> Result<usize, crate::errors::OperationError> {
480            let len = match prefix {
481                HexStringPrefix::Empty => Self::LEN_IN_STR_EXCEPT_PREFIX,
482                HexStringPrefix::WithVersion => Self::LEN_IN_STR,
483            };
484            if out.len() < len {
485                return Err(OperationError::BufferIsTooSmall);
486            }
487            let mut out = out;
488            if prefix == HexStringPrefix::WithVersion {
489                out[0..2].copy_from_slice(b"T1");
490                out = &mut out[2..];
491            }
492            encode_rev_array(out, self.checksum.data());
493            out = &mut out[SIZE_CKSUM * 2..];
494            encode_rev_1(&mut out[0..2], self.lvalue.value());
495            encode_rev_1(&mut out[2..4], self.qratios.value());
496            cfg_if::cfg_if! {
497                if #[cfg(feature = "opt-simd-convert-hex")] {
498                    let _ = hex_simd::encode(
499                        self.body.data(),
500                        hex_simd::Out::from_slice(&mut out[4..]),
501                        hex_simd::AsciiCase::Upper,
502                    );
503                } else {
504                    encode_array(&mut out[4..], self.body.data());
505                }
506            }
507            Ok(len)
508        }
509
510        #[inline]
511        fn max_distance(config: ComparisonConfiguration) -> u32 {
512            FuzzyHashBodyData::<SIZE_BODY>::MAX_DISTANCE
513                + FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::MAX_DISTANCE
514                + FuzzyHashQRatios::MAX_DISTANCE
515                + (match config {
516                    ComparisonConfiguration::Default => FuzzyHashLengthEncoding::MAX_DISTANCE,
517                    ComparisonConfiguration::NoDistance => 0,
518                })
519        }
520
521        #[inline]
522        fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32 {
523            self.body.compare(&other.body)
524                + self.checksum.compare(&other.checksum)
525                + self.qratios.compare(&other.qratios)
526                + (match config {
527                    ComparisonConfiguration::Default => self.lvalue.compare(&other.lvalue),
528                    ComparisonConfiguration::NoDistance => 0,
529                })
530        }
531    }
532
533    impl<
534            const SIZE_CKSUM: usize,
535            const SIZE_BODY: usize,
536            const SIZE_BUCKETS: usize,
537            const SIZE_IN_BYTES: usize,
538            const SIZE_IN_STR_BYTES: usize,
539        > TryFrom<&[u8; SIZE_IN_BYTES]>
540        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
541    where
542        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
543        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
544        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
545        VerboseFuzzyHashParams<
546            SIZE_CKSUM,
547            SIZE_BODY,
548            SIZE_BUCKETS,
549            SIZE_IN_BYTES,
550            SIZE_IN_STR_BYTES,
551        >: ConstrainedVerboseFuzzyHashParams,
552    {
553        type Error = ParseError;
554
555        #[inline]
556        fn try_from(value: &[u8; SIZE_IN_BYTES]) -> Result<Self, Self::Error> {
557            let checksum = FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::from_raw(
558                value[0..SIZE_CKSUM].try_into().unwrap(),
559            );
560            #[cfg(feature = "strict-parser")]
561            if !checksum.is_valid() {
562                return Err(ParseError::InvalidChecksum);
563            }
564            let lvalue = FuzzyHashLengthEncoding::from_raw(value[SIZE_CKSUM]);
565            #[cfg(feature = "strict-parser")]
566            if !lvalue.is_valid() {
567                return Err(ParseError::LengthIsTooLarge);
568            }
569            let qratios = FuzzyHashQRatios::from_raw(value[SIZE_CKSUM + 1]);
570            let value = &value[SIZE_CKSUM + 2..];
571            optionally_unsafe! {
572                invariant!(value.len() == SIZE_BODY);
573            }
574            Ok(Self {
575                checksum,
576                lvalue,
577                qratios,
578                body: FuzzyHashBodyData::from_raw(value.try_into().unwrap()),
579            })
580        }
581    }
582
583    impl<
584            const SIZE_CKSUM: usize,
585            const SIZE_BODY: usize,
586            const SIZE_BUCKETS: usize,
587            const SIZE_IN_BYTES: usize,
588            const SIZE_IN_STR_BYTES: usize,
589        > TryFrom<&[u8]>
590        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
591    where
592        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
593        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
594        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
595        VerboseFuzzyHashParams<
596            SIZE_CKSUM,
597            SIZE_BODY,
598            SIZE_BUCKETS,
599            SIZE_IN_BYTES,
600            SIZE_IN_STR_BYTES,
601        >: ConstrainedVerboseFuzzyHashParams,
602    {
603        type Error = ParseError;
604
605        #[inline(always)]
606        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
607            if value.len() != SIZE_IN_BYTES {
608                return Err(ParseError::InvalidStringLength);
609            }
610            let value: &[u8; SIZE_IN_BYTES] = value.try_into().unwrap();
611            Self::try_from(value)
612        }
613    }
614
615    impl<
616            const SIZE_CKSUM: usize,
617            const SIZE_BODY: usize,
618            const SIZE_BUCKETS: usize,
619            const SIZE_IN_BYTES: usize,
620            const SIZE_IN_STR_BYTES: usize,
621        > FromStr
622        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
623    where
624        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
625        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
626        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
627        VerboseFuzzyHashParams<
628            SIZE_CKSUM,
629            SIZE_BODY,
630            SIZE_BUCKETS,
631            SIZE_IN_BYTES,
632            SIZE_IN_STR_BYTES,
633        >: ConstrainedVerboseFuzzyHashParams,
634    {
635        type Err = ParseError;
636        #[inline(always)]
637        fn from_str(s: &str) -> Result<Self, Self::Err> {
638            Self::from_str_with(s, None)
639        }
640    }
641
642    impl<
643            const SIZE_CKSUM: usize,
644            const SIZE_BODY: usize,
645            const SIZE_BUCKETS: usize,
646            const SIZE_IN_BYTES: usize,
647            const SIZE_IN_STR_BYTES: usize,
648        > Display
649        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
650    where
651        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
652        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
653        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
654        VerboseFuzzyHashParams<
655            SIZE_CKSUM,
656            SIZE_BODY,
657            SIZE_BUCKETS,
658            SIZE_IN_BYTES,
659            SIZE_IN_STR_BYTES,
660        >: ConstrainedVerboseFuzzyHashParams,
661    {
662        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
663            let mut buf = [0u8; SIZE_IN_STR_BYTES];
664            self.store_into_str_bytes(&mut buf, HexStringPrefix::WithVersion)
665                .unwrap();
666            cfg_if::cfg_if! {
667                if #[cfg(feature = "unsafe")] {
668                    unsafe {
669                        f.write_str(core::str::from_utf8_unchecked(&buf))
670                    }
671                } else {
672                    f.write_str(core::str::from_utf8(&buf).unwrap())
673                }
674            }
675        }
676    }
677
678    #[cfg(feature = "serde")]
679    impl<
680            const SIZE_CKSUM: usize,
681            const SIZE_BODY: usize,
682            const SIZE_BUCKETS: usize,
683            const SIZE_IN_BYTES: usize,
684            const SIZE_IN_STR_BYTES: usize,
685        > Serialize
686        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
687    where
688        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
689        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
690        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
691        VerboseFuzzyHashParams<
692            SIZE_CKSUM,
693            SIZE_BODY,
694            SIZE_BUCKETS,
695            SIZE_IN_BYTES,
696            SIZE_IN_STR_BYTES,
697        >: ConstrainedVerboseFuzzyHashParams,
698    {
699        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
700        where
701            S: serde::Serializer,
702        {
703            if serializer.is_human_readable() {
704                let mut buffer = [0u8; SIZE_IN_STR_BYTES];
705                self.store_into_str_bytes(&mut buffer, HexStringPrefix::WithVersion)
706                    .unwrap();
707                #[cfg(feature = "unsafe")]
708                unsafe {
709                    serializer.serialize_str(core::str::from_utf8_unchecked(&buffer))
710                }
711                #[cfg(not(feature = "unsafe"))]
712                {
713                    serializer.serialize_str(core::str::from_utf8(&buffer).unwrap())
714                }
715            } else {
716                let mut buffer = [0u8; SIZE_IN_BYTES];
717                self.store_into_bytes(&mut buffer).unwrap();
718                serializer.serialize_bytes(&buffer)
719            }
720        }
721    }
722
723    /// The visitor struct to handle [fuzzy hash](FuzzyHash) deserialization
724    /// as either a string or a sequence of bytes.
725    ///
726    /// The corresponding visitor implementation handles a fuzzy hash as
727    /// either a string or a sequence of bytes, both representing the string
728    /// representation of that fuzzy hash.
729    ///
730    /// This visitor is used on human-readable formats (such as JSON).
731    #[cfg(feature = "serde")]
732    struct FuzzyHashStringVisitor<
733        const SIZE_CKSUM: usize,
734        const SIZE_BODY: usize,
735        const SIZE_BUCKETS: usize,
736        const SIZE_IN_BYTES: usize,
737        const SIZE_IN_STR_BYTES: usize,
738    >
739    where
740        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
741        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
742        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
743        VerboseFuzzyHashParams<
744            SIZE_CKSUM,
745            SIZE_BODY,
746            SIZE_BUCKETS,
747            SIZE_IN_BYTES,
748            SIZE_IN_STR_BYTES,
749        >: ConstrainedVerboseFuzzyHashParams;
750
751    #[cfg(feature = "serde")]
752    impl<
753            const SIZE_CKSUM: usize,
754            const SIZE_BODY: usize,
755            const SIZE_BUCKETS: usize,
756            const SIZE_IN_BYTES: usize,
757            const SIZE_IN_STR_BYTES: usize,
758        > Visitor<'_>
759        for FuzzyHashStringVisitor<
760            SIZE_CKSUM,
761            SIZE_BODY,
762            SIZE_BUCKETS,
763            SIZE_IN_BYTES,
764            SIZE_IN_STR_BYTES,
765        >
766    where
767        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
768        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
769        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
770        VerboseFuzzyHashParams<
771            SIZE_CKSUM,
772            SIZE_BODY,
773            SIZE_BUCKETS,
774            SIZE_IN_BYTES,
775            SIZE_IN_STR_BYTES,
776        >: ConstrainedVerboseFuzzyHashParams,
777    {
778        type Value =
779            FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>;
780
781        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
782            formatter.write_str("a fuzzy hash string")
783        }
784
785        #[inline]
786        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
787        where
788            E: serde::de::Error,
789        {
790            self.visit_bytes(v.as_bytes())
791        }
792
793        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
794        where
795            E: serde::de::Error,
796        {
797            Self::Value::from_str_bytes(v, None).map_err(serde::de::Error::custom::<ParseError>)
798        }
799    }
800
801    /// The visitor struct to handle [fuzzy hash](FuzzyHash) deserialization
802    /// as a byte sequence.
803    ///
804    /// The corresponding visitor implementation handles a fuzzy hash as
805    /// a plain sequence of bytes.
806    ///
807    /// This visitor is used on machine-friendly formats (such as Postcard).
808    #[cfg(feature = "serde")]
809    struct FuzzyHashBytesVisitor<
810        const SIZE_CKSUM: usize,
811        const SIZE_BODY: usize,
812        const SIZE_BUCKETS: usize,
813        const SIZE_IN_BYTES: usize,
814        const SIZE_IN_STR_BYTES: usize,
815    >
816    where
817        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
818        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
819        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
820        VerboseFuzzyHashParams<
821            SIZE_CKSUM,
822            SIZE_BODY,
823            SIZE_BUCKETS,
824            SIZE_IN_BYTES,
825            SIZE_IN_STR_BYTES,
826        >: ConstrainedVerboseFuzzyHashParams;
827
828    #[cfg(feature = "serde")]
829    impl<
830            const SIZE_CKSUM: usize,
831            const SIZE_BODY: usize,
832            const SIZE_BUCKETS: usize,
833            const SIZE_IN_BYTES: usize,
834            const SIZE_IN_STR_BYTES: usize,
835        > Visitor<'_>
836        for FuzzyHashBytesVisitor<
837            SIZE_CKSUM,
838            SIZE_BODY,
839            SIZE_BUCKETS,
840            SIZE_IN_BYTES,
841            SIZE_IN_STR_BYTES,
842        >
843    where
844        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
845        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
846        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
847        VerboseFuzzyHashParams<
848            SIZE_CKSUM,
849            SIZE_BODY,
850            SIZE_BUCKETS,
851            SIZE_IN_BYTES,
852            SIZE_IN_STR_BYTES,
853        >: ConstrainedVerboseFuzzyHashParams,
854    {
855        type Value =
856            FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>;
857
858        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
859            formatter.write_str("struct FuzzyHash")
860        }
861
862        fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
863        where
864            E: serde::de::Error,
865        {
866            if v.len() != SIZE_IN_BYTES {
867                return Err(serde::de::Error::invalid_length(v.len(), &self));
868            }
869            Ok(Self::Value::try_from(v).unwrap())
870        }
871    }
872
873    #[cfg(feature = "serde")]
874    impl<
875            'de,
876            const SIZE_CKSUM: usize,
877            const SIZE_BODY: usize,
878            const SIZE_BUCKETS: usize,
879            const SIZE_IN_BYTES: usize,
880            const SIZE_IN_STR_BYTES: usize,
881        > Deserialize<'de>
882        for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
883    where
884        FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
885        FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
886        FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
887        VerboseFuzzyHashParams<
888            SIZE_CKSUM,
889            SIZE_BODY,
890            SIZE_BUCKETS,
891            SIZE_IN_BYTES,
892            SIZE_IN_STR_BYTES,
893        >: ConstrainedVerboseFuzzyHashParams,
894    {
895        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
896        where
897            D: serde::Deserializer<'de>,
898        {
899            if deserializer.is_human_readable() {
900                #[cfg(feature = "serde-buffered")]
901                {
902                    deserializer.deserialize_string(
903                        FuzzyHashStringVisitor::<
904                            SIZE_CKSUM,
905                            SIZE_BODY,
906                            SIZE_BUCKETS,
907                            SIZE_IN_BYTES,
908                            SIZE_IN_STR_BYTES,
909                        >,
910                    )
911                }
912                #[cfg(not(feature = "serde-buffered"))]
913                {
914                    deserializer.deserialize_str(
915                        FuzzyHashStringVisitor::<
916                            SIZE_CKSUM,
917                            SIZE_BODY,
918                            SIZE_BUCKETS,
919                            SIZE_IN_BYTES,
920                            SIZE_IN_STR_BYTES,
921                        >,
922                    )
923                }
924            } else {
925                #[cfg(feature = "serde-buffered")]
926                {
927                    deserializer.deserialize_byte_buf(
928                        FuzzyHashBytesVisitor::<
929                            SIZE_CKSUM,
930                            SIZE_BODY,
931                            SIZE_BUCKETS,
932                            SIZE_IN_BYTES,
933                            SIZE_IN_STR_BYTES,
934                        >,
935                    )
936                }
937                #[cfg(not(feature = "serde-buffered"))]
938                {
939                    deserializer.deserialize_bytes(
940                        FuzzyHashBytesVisitor::<
941                            SIZE_CKSUM,
942                            SIZE_BODY,
943                            SIZE_BUCKETS,
944                            SIZE_IN_BYTES,
945                            SIZE_IN_STR_BYTES,
946                        >,
947                    )
948                }
949            }
950        }
951    }
952}
953
954/// Type macro to represent the inner hash type of [`FuzzyHash`]
955/// (an instantiation of [`inner::FuzzyHash`]).
956macro_rules! inner_type {
957    ($size_checksum:expr, $size_buckets:expr) => {
958        <FuzzyHashParams<{$size_checksum}, {$size_buckets}> as ConstrainedFuzzyHashParams>::InnerFuzzyHashType
959    };
960}
961
962/// The fuzzy hash struct representing a fuzzy hash (TLSH).
963///
964/// For the main functionalities, see [`FuzzyHashType`] documentation.
965///
966/// This struct supports conversion from:
967///
968/// *   An array of [`u8`]  
969///     (containing a binary representation as described in
970///     [`FuzzyHashType::store_into_bytes()`]) with the length
971///     [`SIZE_IN_BYTES`](Self::SIZE_IN_BYTES) (through [`TryFrom`]),
972/// *   A slice of [`u8`]  
973///     (containing a binary representation as described in
974///     [`FuzzyHashType::store_into_bytes()`]) with the length
975///     [`SIZE_IN_BYTES`](Self::SIZE_IN_BYTES) (through [`TryFrom`]), or
976/// *   A string  
977///     with the TLSH hexadecimal representation (through [`FromStr`]).
978///
979/// and to:
980///
981/// *   A slice of [`u8`]  
982///     containing a binary representation
983///     using [`FuzzyHashType::store_into_bytes()`] or
984/// *   A string (a slice of [`u8`] or a [`String`])  
985///     with the TLSH hexadecimal representation
986///     using either [`FuzzyHashType::store_into_str_bytes()`] or
987///     through the [`Display`]-based formatting (including [`ToString`]).
988#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub struct FuzzyHash<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize>
990where
991    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
992{
993    /// The inner object representing actual contents of the fuzzy hash.
994    inner: inner_type!(SIZE_CKSUM, SIZE_BUCKETS),
995}
996
997impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
998where
999    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1000{
1001    /// Creates an object from the inner object.
1002    #[inline(always)]
1003    pub(crate) fn new(inner: inner_type!(SIZE_CKSUM, SIZE_BUCKETS)) -> Self {
1004        Self { inner }
1005    }
1006}
1007
1008impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> crate::FuzzyHashType
1009    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1010where
1011    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1012{
1013    type ChecksumType = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as FuzzyHashType>::ChecksumType;
1014    type BodyType = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as FuzzyHashType>::BodyType;
1015
1016    const NUMBER_OF_BUCKETS: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::NUMBER_OF_BUCKETS;
1017    const SIZE_IN_BYTES: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::SIZE_IN_BYTES;
1018    const LEN_IN_STR_EXCEPT_PREFIX: usize =
1019        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::LEN_IN_STR_EXCEPT_PREFIX;
1020    const LEN_IN_STR: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::LEN_IN_STR;
1021    #[inline(always)]
1022    fn from_str_bytes(
1023        bytes: &[u8],
1024        prefix: Option<HexStringPrefix>,
1025    ) -> Result<Self, crate::errors::ParseError> {
1026        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::from_str_bytes(bytes, prefix)
1027            .map(|inner| Self { inner })
1028    }
1029    #[inline(always)]
1030    fn checksum(&self) -> &Self::ChecksumType {
1031        self.inner.checksum()
1032    }
1033    #[inline(always)]
1034    fn length(&self) -> &FuzzyHashLengthEncoding {
1035        self.inner.length()
1036    }
1037    #[inline(always)]
1038    fn qratios(&self) -> &FuzzyHashQRatios {
1039        self.inner.qratios()
1040    }
1041    #[inline(always)]
1042    fn body(&self) -> &Self::BodyType {
1043        self.inner.body()
1044    }
1045    #[inline(always)]
1046    fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, crate::errors::OperationError> {
1047        self.inner.store_into_bytes(out)
1048    }
1049    #[inline(always)]
1050    fn store_into_str_bytes(
1051        &self,
1052        out: &mut [u8],
1053        prefix: HexStringPrefix,
1054    ) -> Result<usize, OperationError> {
1055        self.inner.store_into_str_bytes(out, prefix)
1056    }
1057    #[inline(always)]
1058    fn max_distance(config: ComparisonConfiguration) -> u32 {
1059        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::max_distance(config)
1060    }
1061    #[inline(always)]
1062    fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32 {
1063        self.inner.compare_with_config(&other.inner, config)
1064    }
1065}
1066impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Display
1067    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1068where
1069    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1070{
1071    #[inline(always)]
1072    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1073        self.inner.fmt(f)
1074    }
1075}
1076impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> FromStr
1077    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1078where
1079    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1080    inner_type!(SIZE_CKSUM, SIZE_BUCKETS): FromStr<Err = ParseError>,
1081{
1082    type Err = ParseError;
1083    #[inline(always)]
1084    fn from_str(s: &str) -> Result<Self, Self::Err> {
1085        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::from_str(s).map(Self::new)
1086    }
1087}
1088impl<'a, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize, const SIZE_IN_BYTES: usize>
1089    TryFrom<&'a [u8; SIZE_IN_BYTES]> for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1090where
1091    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1092    inner_type!(SIZE_CKSUM, SIZE_BUCKETS): TryFrom<&'a [u8; SIZE_IN_BYTES], Error = ParseError>,
1093{
1094    type Error = ParseError;
1095    #[inline(always)]
1096    fn try_from(value: &'a [u8; SIZE_IN_BYTES]) -> Result<Self, Self::Error> {
1097        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::try_from(value).map(Self::new)
1098    }
1099}
1100impl<'a, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> TryFrom<&'a [u8]>
1101    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1102where
1103    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1104    inner_type!(SIZE_CKSUM, SIZE_BUCKETS): TryFrom<&'a [u8], Error = ParseError>,
1105{
1106    type Error = ParseError;
1107    #[inline(always)]
1108    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
1109        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::try_from(value).map(Self::new)
1110    }
1111}
1112#[cfg(feature = "serde")]
1113impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Serialize
1114    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1115where
1116    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1117    inner_type!(SIZE_CKSUM, SIZE_BUCKETS): Serialize,
1118{
1119    #[inline(always)]
1120    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1121    where
1122        S: serde::Serializer,
1123    {
1124        // Wrap inner implementation
1125        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as Serialize>::serialize(&self.inner, serializer)
1126    }
1127}
1128#[cfg(feature = "serde")]
1129impl<'de, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Deserialize<'de>
1130    for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1131where
1132    FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1133    inner_type!(SIZE_CKSUM, SIZE_BUCKETS): Deserialize<'de>,
1134{
1135    #[inline(always)]
1136    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1137    where
1138        D: serde::Deserializer<'de>,
1139    {
1140        // Wrap inner implementation
1141        <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as Deserialize<'de>>::deserialize(deserializer)
1142            .map(Self::new)
1143    }
1144}
1145
1146mod tests;