tlsh/
errors.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// SPDX-FileCopyrightText: Copyright (C) 2024 Tsukasa OI <floss_ssdeep@irq.a4lg.com>.
3
4//! Types representing specific types of errors.
5
6use core::fmt::{Display, Formatter, Result};
7
8/// An error type representing an error (generally) while parsing a fuzzy hash.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum ParseError {
12    /// The data length field is too large.
13    ///
14    /// On the string parser or deserializer, this error will not be generated
15    /// unless the strict parser is enabled.
16    LengthIsTooLarge,
17    /// Invalid prefix is encountered.
18    ///
19    /// This type of error is generated when we need a prefix but cannot find
20    /// a valid one (TLSHv1 `"T1"`).
21    InvalidPrefix,
22    /// An invalid character is encountered.
23    InvalidCharacter,
24    /// The string length is invalid.
25    InvalidStringLength,
26    /// The checksum part contained an invalid value.
27    InvalidChecksum,
28}
29impl Display for ParseError {
30    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
31        f.write_str(match self {
32            ParseError::LengthIsTooLarge => "length field is too large",
33            ParseError::InvalidPrefix => "encountered an invalid prefix",
34            ParseError::InvalidCharacter => "encountered an invalid character",
35            ParseError::InvalidStringLength => "string length is invalid",
36            ParseError::InvalidChecksum => "has an invalid checksum field",
37        })
38    }
39}
40#[cfg(feature = "std")]
41#[cfg_attr(feature = "unstable", doc(cfg(all())))]
42impl std::error::Error for ParseError {}
43#[cfg(all(not(feature = "std"), fast_tlsh_error_in_core = "stable"))]
44impl core::error::Error for ParseError {}
45
46/// An error type representing an error (generally) while processing a fuzzy hash.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum OperationError {
50    /// The buffer you specified is too small to finish the operation successfully.
51    ///
52    /// The reason of this error type is because the buffer (slice) you have
53    /// specified is too small for the output format you requested.
54    BufferIsTooSmall,
55}
56impl Display for OperationError {
57    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
58        f.write_str(match self {
59            OperationError::BufferIsTooSmall => "buffer is too small to store the result",
60        })
61    }
62}
63#[cfg(feature = "std")]
64#[cfg_attr(feature = "unstable", doc(cfg(all())))]
65impl std::error::Error for OperationError {}
66#[cfg(all(not(feature = "std"), fast_tlsh_error_in_core = "stable"))]
67impl core::error::Error for OperationError {}
68
69/// An error category type for [a generator error](GeneratorError).
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum GeneratorErrorCategory {
73    /// The error is (mainly) about the length of the data.
74    DataLength,
75
76    /// The error is (mainly) about the distribution of the data
77    /// (or, repetitiveness).
78    DataDistribution,
79}
80
81/// An error type representing an error while generating a fuzzy hash.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum GeneratorError {
85    /// The input data is too large to process.
86    TooLargeInput,
87    /// The input data is too small to process.
88    ///
89    /// Whether the input data is too small is normally determined by the value
90    /// of [`DataLengthProcessingMode`](crate::length::DataLengthProcessingMode).
91    ///
92    /// If we prefer compatibility with the original TLSH implementation,
93    /// we cannot generate a fuzzy hash from the data smaller than 50 bytes.
94    TooSmallInput,
95    /// Too many buckets (roughly half or more) are empty.
96    ///
97    /// This error indicates the input data is either too small or too
98    /// repetitive so that enough number of buckets cannot be filled
99    /// (i.e. even if we force to output a fuzzy hash, the result might be
100    /// statistically unreliable).
101    BucketsAreHalfEmpty,
102    /// Too many buckets (roughly 3/4 or more) are empty.
103    ///
104    /// This is similar to [`BucketsAreHalfEmpty`](Self::BucketsAreHalfEmpty)
105    /// but indicates more extreme statistic distribution so that computing
106    /// a Q ratio will result in a division by zero.
107    BucketsAreThreeQuarterEmpty,
108}
109impl GeneratorError {
110    /// Retrieves the category of the generator error.
111    pub fn category(&self) -> GeneratorErrorCategory {
112        match *self {
113            GeneratorError::TooLargeInput => GeneratorErrorCategory::DataLength,
114            GeneratorError::TooSmallInput => GeneratorErrorCategory::DataLength,
115            GeneratorError::BucketsAreHalfEmpty => GeneratorErrorCategory::DataDistribution,
116            GeneratorError::BucketsAreThreeQuarterEmpty => GeneratorErrorCategory::DataDistribution,
117        }
118    }
119}
120impl Display for GeneratorError {
121    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
122        f.write_str(match self {
123            GeneratorError::TooLargeInput => "input data is too large to process",
124            GeneratorError::TooSmallInput => "input data is too small to process",
125            GeneratorError::BucketsAreHalfEmpty => {
126                "approximately half or more effective buckets are empty"
127            }
128            GeneratorError::BucketsAreThreeQuarterEmpty => {
129                "approximately 3/4 or more effective buckets are empty"
130            }
131        })
132    }
133}
134#[cfg(feature = "std")]
135#[cfg_attr(feature = "unstable", doc(cfg(all())))]
136impl std::error::Error for GeneratorError {}
137#[cfg(all(not(feature = "std"), fast_tlsh_error_in_core = "stable"))]
138impl core::error::Error for GeneratorError {}
139
140/// The operand (side) which caused a parse error.
141#[cfg(feature = "easy-functions")]
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum ParseErrorSide {
144    /// The left hand side.
145    Left,
146    /// The right hand side.
147    Right,
148}
149
150/// The error type representing a parse error for one of the operands
151/// specified to the [`compare()`](crate::compare()) function.
152#[cfg(feature = "easy-functions")]
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub struct ParseErrorEither(pub(crate) ParseErrorSide, pub(crate) ParseError);
155#[cfg(feature = "easy-functions")]
156impl ParseErrorEither {
157    /// Returns which operand caused a parse error.
158    pub fn side(&self) -> ParseErrorSide {
159        self.0
160    }
161
162    /// Returns the inner error.
163    pub fn inner_err(&self) -> ParseError {
164        self.1
165    }
166}
167#[cfg(feature = "easy-functions")]
168impl Display for ParseErrorEither {
169    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
170        write!(
171            f,
172            "error occurred while parsing fuzzy hash {side} ({msg})",
173            side = match self.side() {
174                ParseErrorSide::Left => 1,
175                ParseErrorSide::Right => 2,
176            },
177            msg = self.inner_err()
178        )
179    }
180}
181#[cfg(all(feature = "easy-functions", feature = "std"))]
182#[cfg_attr(feature = "unstable", doc(cfg(all())))]
183impl std::error::Error for ParseErrorEither {}
184#[cfg(all(
185    feature = "easy-functions",
186    not(feature = "std"),
187    fast_tlsh_error_in_core = "stable"
188))]
189impl core::error::Error for ParseErrorEither {}
190
191/// The error type describing either a generator error or an I/O error.
192///
193/// This type contains either:
194/// *   A fuzzy hash generator error ([`GeneratorError`]) or
195/// *   An I/O error ([`std::io::Error`]).
196#[cfg(all(feature = "easy-functions", feature = "std"))]
197#[derive(Debug)]
198pub enum GeneratorOrIOError {
199    /// An error caused by the fuzzy hash generator.
200    GeneratorError(GeneratorError),
201    /// An error caused by an internal I/O operation.
202    IOError(std::io::Error),
203}
204#[cfg(all(feature = "easy-functions", feature = "std"))]
205impl Display for GeneratorOrIOError {
206    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
207        match self {
208            GeneratorOrIOError::GeneratorError(err) => err.fmt(f),
209            GeneratorOrIOError::IOError(err) => err.fmt(f),
210        }
211    }
212}
213#[cfg(all(feature = "easy-functions", feature = "std"))]
214impl From<GeneratorError> for GeneratorOrIOError {
215    // For wrapping with the '?' operator
216    fn from(value: GeneratorError) -> Self {
217        GeneratorOrIOError::GeneratorError(value)
218    }
219}
220#[cfg(all(feature = "easy-functions", feature = "std"))]
221impl From<std::io::Error> for GeneratorOrIOError {
222    // For wrapping with the '?' operator
223    fn from(value: std::io::Error) -> Self {
224        GeneratorOrIOError::IOError(value)
225    }
226}
227#[cfg(all(feature = "easy-functions", feature = "std"))]
228impl std::error::Error for GeneratorOrIOError {
229    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
230        match self {
231            GeneratorOrIOError::GeneratorError(err) => Some(err),
232            GeneratorOrIOError::IOError(err) => Some(err),
233        }
234    }
235}
236
237mod tests;