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("Bad length of directory chunk, expected multiple of {DIRECTORY_ENTRY_LEN}, found {0}")]
87 InvalidDirectoryChunkLen(u64),
88
89 #[error(
90 "Unsorted directory entry {entry_index} has name {} but comes after name \
91 {}",
92 format_path_for_error(name),
93 format_path_for_error(previous_name)
94 )]
95 DirectoryEntriesOutOfOrder { entry_index: usize, name: Vec<u8>, previous_name: Vec<u8> },
96
97 #[error("Directory entry has a zero length name")]
98 ZeroLengthName,
99
100 #[error("Directory entry name starts with '/' {}", format_path_for_error(.0))]
101 NameStartsWithSlash(Vec<u8>),
102
103 #[error("Directory entry name ends with '/' {}", format_path_for_error(.0))]
104 NameEndsWithSlash(Vec<u8>),
105
106 #[error("Directory entry name contains the null character {}", format_path_for_error(.0))]
107 NameContainsNull(Vec<u8>),
108
109 #[error(
110 "Directory entry name contains an empty segment (consecutive '/') {}",
111 format_path_for_error(.0)
112 )]
113 NameContainsEmptySegment(Vec<u8>),
114
115 #[error("Directory entry name contains a segment of '.' {}", format_path_for_error(.0))]
116 NameContainsDotSegment(Vec<u8>),
117
118 #[error("Directory entry name contains a segment of '..' {}", format_path_for_error(.0))]
119 NameContainsDotDotSegment(Vec<u8>),
120
121 #[error(
122 "Path data for directory entry {entry_index} has offset {offset} larger than \
123 directory names chunk size {chunk_size}"
124 )]
125 PathDataOffsetTooLarge { entry_index: usize, offset: usize, chunk_size: usize },
126
127 #[error(
128 "Path data for directory entry {entry_index} has offset {offset} plus length \
129 {length} larger than directory names chunk size {chunk_size}"
130 )]
131 PathDataLengthTooLarge { entry_index: usize, offset: usize, length: u16, chunk_size: usize },
132
133 #[error("Path data not utf8: {path:?}")]
134 PathDataInvalidUtf8 { source: std::str::Utf8Error, path: Vec<u8> },
135
136 #[error("Path not present in archive: {}", format_path_for_error(.0))]
137 PathNotPresent(Vec<u8>),
138
139 #[error("Attempted to read past the end of a content chunk")]
140 ReadPastEnd,
141
142 #[error("Content chunk offset larger than u64::MAX")]
143 ContentChunkOffsetOverflow,
144
145 #[error(
146 "Directory entry for {} has a bad content chunk offset, expected {expected} actual \
147 {actual}",
148 format_path_for_error(name)
149 )]
150 InvalidContentChunkOffset { name: Vec<u8>, expected: u64, actual: u64 },
151
152 #[error(
153 "Directory entry for {} implies a content chunk end that overflows u64, offset: \
154 {offset}, length: {length}",
155 format_path_for_error(name)
156 )]
157 ContentChunkEndOverflow { name: Vec<u8>, offset: u64, length: u64 },
158
159 #[error(
160 "Archive has {archive_size} bytes, but content chunk (including padding) for {} ends \
161 at {lower_bound}",
162 format_path_for_error(name)
163 )]
164 ContentChunkBeyondArchive { name: Vec<u8>, lower_bound: u64, archive_size: u64 },
165
166 #[error(
167 "The content chunk for {} is {chunk_size} bytes but the system does not support \
168 buffers larger than {} bytes",
169 format_path_for_error(name),
170 usize::MAX
171 )]
172 ContentChunkDoesNotFitInMemory { name: Vec<u8>, chunk_size: u64 },
173}
174
175fn ascii_chunk(bytes: &[u8; 8]) -> String {
178 let v: Vec<u8> = bytes.iter().copied().flat_map(std::ascii::escape_default).collect();
179 String::from_utf8_lossy(&v).into_owned()
180}
181
182fn format_path_for_error(path: &[u8]) -> String {
184 std::str::from_utf8(path).map(|s| format!("{s:?}")).unwrap_or_else(|_| format!("{path:?}"))
185}
186
187#[cfg(test)]
188mod tests {
189 #[test]
190 fn ascii_chunk() {
191 assert_eq!(
192 super::ascii_chunk(&[b'A', 0, b'b', b'\\', 255, b'-', b'_', b'\n']),
193 r"A\x00b\\\xff-_\n"
194 );
195 }
196}