bao_tree/io/
error.rs

1//! Errors when encoding or decoding
2//!
3//! These erros contain more specific information about e.g. where a hash mismatch occured
4use std::{fmt, io};
5
6use crate::{ChunkNum, TreeNode};
7
8/// Error when decoding from a reader, after the size has been read
9#[derive(Debug)]
10pub enum DecodeError {
11    /// We got an EOF while reading a parent hash pair, indicating that the remote end does not have the outboard
12    ParentNotFound(TreeNode),
13    /// We got an EOF while reading a chunk, indicating that the remote end does not have the data
14    LeafNotFound(ChunkNum),
15    /// The hash of a parent did not match the expected hash
16    ParentHashMismatch(TreeNode),
17    /// The hash of a leaf did not match the expected hash
18    LeafHashMismatch(ChunkNum),
19    /// There was an error reading from the underlying io
20    Io(io::Error),
21}
22
23impl fmt::Display for DecodeError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        fmt::Debug::fmt(self, f)
26    }
27}
28
29impl std::error::Error for DecodeError {
30    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
31        match self {
32            Self::Io(e) => Some(e),
33            _ => None,
34        }
35    }
36}
37
38impl From<io::Error> for DecodeError {
39    fn from(e: io::Error) -> Self {
40        Self::Io(e)
41    }
42}
43
44impl From<DecodeError> for io::Error {
45    fn from(e: DecodeError) -> Self {
46        match e {
47            DecodeError::Io(e) => e,
48            DecodeError::ParentHashMismatch(node) => io::Error::new(
49                io::ErrorKind::InvalidData,
50                format!(
51                    "parent hash mismatch (level {}, block {})",
52                    node.level(),
53                    node.mid().0
54                ),
55            ),
56            DecodeError::LeafHashMismatch(chunk) => io::Error::new(
57                io::ErrorKind::InvalidData,
58                format!("leaf hash mismatch (offset {})", chunk.to_bytes()),
59            ),
60            DecodeError::LeafNotFound(_) => io::Error::new(io::ErrorKind::UnexpectedEof, e),
61            DecodeError::ParentNotFound(_) => io::Error::new(io::ErrorKind::UnexpectedEof, e),
62        }
63    }
64}
65
66impl DecodeError {
67    pub(crate) fn maybe_parent_not_found(e: io::Error, node: TreeNode) -> Self {
68        if e.kind() == io::ErrorKind::UnexpectedEof {
69            Self::ParentNotFound(node)
70        } else {
71            Self::Io(e)
72        }
73    }
74
75    pub(crate) fn maybe_leaf_not_found(e: io::Error, chunk: ChunkNum) -> Self {
76        if e.kind() == io::ErrorKind::UnexpectedEof {
77            Self::LeafNotFound(chunk)
78        } else {
79            Self::Io(e)
80        }
81    }
82}
83
84/// Error when encoding from outboard and data
85///
86/// This can either be a io error or a more specific error like a hash mismatch
87/// or a size mismatch. If the remote end stops listening while we are writing,
88/// the error will indicate which parent or chunk we were writing at the time.
89#[derive(Debug)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub enum EncodeError {
92    /// The hash of a parent did not match the expected hash
93    ParentHashMismatch(TreeNode),
94    /// The hash of a leaf did not match the expected hash
95    LeafHashMismatch(ChunkNum),
96    /// We got a ConnectionReset while writing a parent hash pair, indicating that the remote end stopped listening
97    ParentWrite(TreeNode),
98    /// We got a ConnectionReset while writing a chunk, indicating that the remote end stopped listening
99    LeafWrite(ChunkNum),
100    /// File size does not match size in outboard
101    SizeMismatch,
102    /// There was an error reading from the underlying io
103    #[cfg_attr(feature = "serde", serde(with = "crate::io_error_serde"))]
104    Io(io::Error),
105}
106
107impl fmt::Display for EncodeError {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        fmt::Debug::fmt(self, f)
110    }
111}
112
113impl std::error::Error for EncodeError {
114    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
115        match self {
116            EncodeError::Io(e) => Some(e),
117            _ => None,
118        }
119    }
120}
121
122impl From<EncodeError> for io::Error {
123    fn from(e: EncodeError) -> Self {
124        match e {
125            EncodeError::Io(e) => e,
126            EncodeError::ParentHashMismatch(node) => io::Error::new(
127                io::ErrorKind::InvalidData,
128                format!(
129                    "parent hash mismatch (level {}, block {})",
130                    node.level(),
131                    node.mid().0
132                ),
133            ),
134            EncodeError::LeafHashMismatch(chunk) => io::Error::new(
135                io::ErrorKind::InvalidData,
136                format!("leaf hash mismatch at {}", chunk.to_bytes()),
137            ),
138            EncodeError::ParentWrite(node) => io::Error::new(
139                io::ErrorKind::ConnectionReset,
140                format!(
141                    "parent write failed (level {}, block {})",
142                    node.level(),
143                    node.mid().0
144                ),
145            ),
146            EncodeError::LeafWrite(chunk) => io::Error::new(
147                io::ErrorKind::ConnectionReset,
148                format!("leaf write failed at {}", chunk.to_bytes()),
149            ),
150            EncodeError::SizeMismatch => {
151                io::Error::new(io::ErrorKind::InvalidData, "size mismatch")
152            }
153        }
154    }
155}
156
157impl From<io::Error> for EncodeError {
158    fn from(e: io::Error) -> Self {
159        Self::Io(e)
160    }
161}
162
163impl EncodeError {
164    #[cfg(feature = "tokio_fsm")]
165    pub(crate) fn maybe_parent_write(e: io::Error, node: TreeNode) -> Self {
166        if e.kind() == io::ErrorKind::ConnectionReset {
167            Self::ParentWrite(node)
168        } else {
169            Self::Io(e)
170        }
171    }
172
173    #[cfg(feature = "tokio_fsm")]
174    pub(crate) fn maybe_leaf_write(e: io::Error, chunk: ChunkNum) -> Self {
175        if e.kind() == io::ErrorKind::ConnectionReset {
176            Self::LeafWrite(chunk)
177        } else {
178            Self::Io(e)
179        }
180    }
181}