iris_format/error.rs
1//! What can go wrong while reading a container.
2
3use iris_abi::Tag;
4
5/// Why a container could not be read, or could not be trusted once it was read.
6///
7/// Every variant carries enough to put in a log line without going back to the file. An operator
8/// who gets one of these should be able to say what is wrong with the dataset without opening a hex
9/// editor, because the alternative is that they open a hex editor.
10#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum Error {
13 /// The file does not start with the container magic.
14 #[error("this is not an iris container: it starts with {found:02x?} and not {expected:02x?}")]
15 NotAContainer {
16 /// The first eight bytes of the file, or fewer if the file is shorter than that.
17 found: Vec<u8>,
18 /// What those bytes should have been.
19 expected: Vec<u8>,
20 },
21 /// The file starts correctly but ends somewhere unexpected.
22 #[error("the container is truncated: {what} needs {needed} bytes and there are {available}")]
23 Truncated {
24 /// Which part of the file ran out.
25 what: &'static str,
26 /// How many bytes that part needed.
27 needed: u64,
28 /// How many were there.
29 available: u64,
30 },
31 /// The container was written by a newer major version.
32 #[error(
33 "this container is format {major}.{minor} and this build reads format {supported_major}.x"
34 )]
35 UnsupportedFormat {
36 /// The major version in the file.
37 major: u16,
38 /// The minor version in the file.
39 minor: u16,
40 /// The major version this build understands.
41 supported_major: u16,
42 },
43 /// A reserved field was not zero.
44 ///
45 /// Reserved fields are the only place a future version can put a change that older readers must
46 /// not ignore, so an older reader has to refuse rather than carry on.
47 #[error(
48 "a reserved field in the {what} is not zero, so this container uses something this build does not know about"
49 )]
50 Reserved {
51 /// Which part of the container the field is in.
52 what: &'static str,
53 },
54 /// A section points somewhere that is not inside the payload area.
55 #[error(
56 "section {id} covers bytes {offset}..{end} which is not inside the payload area of a {file_len} byte container"
57 )]
58 SectionOutOfBounds {
59 /// Which section.
60 id: u32,
61 /// Where it claims to start.
62 offset: u64,
63 /// Where it claims to end, saturated at `u64::MAX`.
64 end: u64,
65 /// How long the file actually is.
66 file_len: u64,
67 },
68 /// Two sections claim the same identifier.
69 #[error("section id {id} is used twice, so a reference to it is ambiguous")]
70 DuplicateSection {
71 /// The repeated identifier.
72 id: u32,
73 },
74 /// A required footer record was missing.
75 #[error("the footer has no {0} record")]
76 MissingRecord(Tag),
77 /// A footer record appeared more times than it is allowed to.
78 #[error("the footer has more than one {0} record")]
79 RepeatedRecord(Tag),
80 /// A known footer record was written at a version this build does not read.
81 #[error("the footer has a {tag} record at version {version}, which this build does not read")]
82 UnsupportedRecord {
83 /// Which record.
84 tag: Tag,
85 /// The version on it.
86 version: u16,
87 },
88 /// A digest did not match the bytes it covers.
89 #[error(
90 "the {what} digest does not match: the footer says {expected} and the bytes hash to {actual}"
91 )]
92 DigestMismatch {
93 /// Which digest.
94 what: String,
95 /// What the container claims.
96 expected: String,
97 /// What the bytes actually hash to.
98 actual: String,
99 },
100 /// The bytes inside the footer did not parse.
101 #[error("the footer is malformed: {0}")]
102 Footer(#[from] iris_abi::Error),
103 /// Something in the container is bigger than this build can address.
104 ///
105 /// On a 64 bit host this cannot happen. It is here so that a 32 bit host gets a sentence
106 /// instead of a panic.
107 #[error(
108 "the container declares {needed} bytes for {what}, which does not fit in this build's address space"
109 )]
110 TooLarge {
111 /// Which part of the container.
112 what: &'static str,
113 /// How many bytes it asked for.
114 needed: u64,
115 },
116 /// Writing a container out did not work.
117 ///
118 /// Only [`crate::Builder::build_into`] produces this. Reading never touches a file: a container
119 /// is parsed from bytes somebody else fetched, which is what lets the same parser run against a
120 /// mapped file, a buffer and a fuzzer's input.
121 ///
122 /// The kind is kept and the error itself is not, because this enum compares by value and
123 /// [`std::io::Error`] does not. The kind is the part anybody branches on anyway, and the text is
124 /// there for whoever reads the message.
125 #[error("the container could not be written: {detail}")]
126 Io {
127 /// What went wrong, as the operating system classified it.
128 kind: std::io::ErrorKind,
129 /// What it said.
130 detail: String,
131 },
132}
133
134impl Error {
135 /// Wraps a failure from the writer a container is being written to.
136 pub(crate) fn io(err: &std::io::Error) -> Self {
137 Self::Io {
138 kind: err.kind(),
139 detail: err.to_string(),
140 }
141 }
142}
143
144/// The result of reading a container.
145pub type Result<T> = core::result::Result<T, Error>;