1use crate::{ChunkType, DIRECTORY_ENTRY_LEN, INDEX_ENTRY_LEN};
6use std::io;
7
8#[non_exhaustive]
9#[derive(thiserror::Error, Debug)]
10pub enum Error {
11 #[error("Names can be no longer than 65,535 bytes, supplied name was {0} bytes")]
12 NameTooLong(usize),
13
14 #[error("The length of the concatenated path data must fit in a u32")]
15 TooMuchPathData,
16
17 #[error("Writing archive")]
18 Write(#[source] io::Error),
19
20 #[error("Copying content chunk to archive")]
21 Copy(#[source] io::Error),
22
23 #[error("Seeking within archive")]
24 Seek(#[source] io::Error),
25
26 #[error("Reading archive")]
27 Read(#[source] io::Error),
28
29 #[error("Getting archive size")]
30 GetSize(#[source] io::Error),
31
32 #[error(
33 "Content chunk had expected size {expected} but Reader supplied {actual} at archive \
34 path: {}",
35 format_path_for_error(path)
36 )]
37 ContentChunkSizeMismatch { expected: u64, actual: u64, path: Vec<u8> },
38
39 #[error("Missing directory chunk index entry")]
40 MissingDirectoryChunkIndexEntry,
41
42 #[error("Missing directory names chunk index entry")]
43 MissingDirectoryNamesChunkIndexEntry,
44
45 #[error("Serialize index")]
46 SerializeIndex(#[source] std::io::Error),
47
48 #[error("Serialize directory chunk index entry")]
49 SerializeDirectoryChunkIndexEntry(#[source] std::io::Error),
50
51 #[error("Serialize directory names chunk index entry")]
52 SerializeDirectoryNamesChunkIndexEntry(#[source] std::io::Error),
53
54 #[error("Serialize directory entry")]
55 SerializeDirectoryEntry(#[source] std::io::Error),
56
57 #[error("Invalid magic bytes, expected [c8, bf, 0b, 48, ad, ab, c5, 11], found {0:02x?}")]
58 InvalidMagic([u8; 8]),
59
60 #[error("Bad length of index entries, expected multiple of {INDEX_ENTRY_LEN}, found {0}")]
61 InvalidIndexEntriesLen(u64),
62
63 #[error(
64 "Index entries not strictly increasing, {} followed by {}",
65 ascii_chunk(prev),
66 ascii_chunk(next)
67 )]
68 IndexEntriesOutOfOrder { prev: ChunkType, next: ChunkType },
69
70 #[error(
71 "Invalid chunk offset for chunk {}, expected {expected}, actual {actual}",
72 ascii_chunk(chunk_type)
73 )]
74 InvalidChunkOffset { chunk_type: ChunkType, expected: u64, actual: u64 },
75
76 #[error(
77 "Invalid chunk length for chunk {}, offset + length overflows, offset: {offset}, length: \
78 {length}",
79 ascii_chunk(chunk_type)
80 )]
81 InvalidChunkLength { chunk_type: ChunkType, offset: u64, length: u64 },
82
83 #[error("Bad length of directory names chunk, expected multiple of 8, found {0}")]
84 InvalidDirectoryNamesChunkLen(u64),
85
86 #[error(
87 "Bad length of directory chunk, expected multiple of {DIRECTORY_ENTRY_LEN}, found {0}"
88 )]
89 InvalidDirectoryChunkLen(u64),
90
91 #[error(
92 "Unsorted directory entry {entry_index} has name {} but comes after name \
93 {}",
94 format_path_for_error(name),
95 format_path_for_error(previous_name)
96 )]
97 DirectoryEntriesOutOfOrder { entry_index: usize, name: Vec<u8>, previous_name: Vec<u8> },
98
99 #[error("Directory entry has a zero length name")]
100 ZeroLengthName,
101
102 #[error("Directory entry name starts with '/' {}", format_path_for_error(.0))]
103 NameStartsWithSlash(Vec<u8>),
104
105 #[error("Directory entry name ends with '/' {}", format_path_for_error(.0))]
106 NameEndsWithSlash(Vec<u8>),
107
108 #[error("Directory entry name contains the null character {}", format_path_for_error(.0))]
109 NameContainsNull(Vec<u8>),
110
111 #[error(
112 "Directory entry name contains an empty segment (consecutive '/') {}",
113 format_path_for_error(.0)
114 )]
115 NameContainsEmptySegment(Vec<u8>),
116
117 #[error("Directory entry name contains a segment of '.' {}", format_path_for_error(.0))]
118 NameContainsDotSegment(Vec<u8>),
119
120 #[error("Directory entry name contains a segment of '..' {}", format_path_for_error(.0))]
121 NameContainsDotDotSegment(Vec<u8>),
122
123 #[error(
124 "Path data for directory entry {entry_index} has offset {offset} larger than \
125 directory names chunk size {chunk_size}"
126 )]
127 PathDataOffsetTooLarge { entry_index: usize, offset: usize, chunk_size: usize },
128
129 #[error(
130 "Path data for directory entry {entry_index} has offset {offset} plus length \
131 {length} larger than directory names chunk size {chunk_size}"
132 )]
133 PathDataLengthTooLarge { entry_index: usize, offset: usize, length: u16, chunk_size: usize },
134
135 #[error("Path data not utf8: {path:?}")]
136 PathDataInvalidUtf8 { source: std::str::Utf8Error, path: Vec<u8> },
137
138 #[error("Path not present in archive: {}", format_path_for_error(.0))]
139 PathNotPresent(Vec<u8>),
140
141 #[error("Attempted to read past the end of a content chunk")]
142 ReadPastEnd,
143
144 #[error("Content chunk offset larger than u64::MAX")]
145 ContentChunkOffsetOverflow,
146
147 #[error(
148 "Directory entry for {} has a bad content chunk offset, expected {expected} actual \
149 {actual}",
150 format_path_for_error(name)
151 )]
152 InvalidContentChunkOffset { name: Vec<u8>, expected: u64, actual: u64 },
153
154 #[error(
155 "Directory entry for {} implies a content chunk end that overflows u64, offset: \
156 {offset}, length: {length}",
157 format_path_for_error(name)
158 )]
159 ContentChunkEndOverflow { name: Vec<u8>, offset: u64, length: u64 },
160
161 #[error(
162 "Archive has {archive_size} bytes, but content chunk (including padding) for {} ends \
163 at {lower_bound}",
164 format_path_for_error(name)
165 )]
166 ContentChunkBeyondArchive { name: Vec<u8>, lower_bound: u64, archive_size: u64 },
167
168 #[error(
169 "The content chunk for {} is {chunk_size} bytes but the system does not support \
170 buffers larger than {} bytes",
171 format_path_for_error(name),
172 usize::MAX
173 )]
174 ContentChunkDoesNotFitInMemory { name: Vec<u8>, chunk_size: u64 },
175}
176
177fn ascii_chunk(bytes: &[u8; 8]) -> String {
180 let v: Vec<u8> = bytes.iter().copied().flat_map(std::ascii::escape_default).collect();
181 String::from_utf8_lossy(&v).into_owned()
182}
183
184fn format_path_for_error(path: &[u8]) -> String {
186 std::str::from_utf8(path).map(|s| format!("{s:?}")).unwrap_or_else(|_| format!("{path:?}"))
187}
188
189#[cfg(test)]
190mod tests {
191 #[test]
192 fn ascii_chunk() {
193 assert_eq!(
194 super::ascii_chunk(&[b'A', 0, b'b', b'\\', 255, b'-', b'_', b'\n']),
195 r"A\x00b\\\xff-_\n"
196 );
197 }
198}