use crate::{ChunkType, DIRECTORY_ENTRY_LEN, INDEX_ENTRY_LEN};
use std::io;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Names can be no longer than 65,535 bytes, supplied name was {0} bytes")]
NameTooLong(usize),
#[error("The length of the concatenated path data must fit in a u32")]
TooMuchPathData,
#[error("Writing archive")]
Write(#[source] io::Error),
#[error("Copying content chunk to archive")]
Copy(#[source] io::Error),
#[error("Seeking within archive")]
Seek(#[source] io::Error),
#[error("Reading archive")]
Read(#[source] io::Error),
#[error("Getting archive size")]
GetSize(#[source] io::Error),
#[error(
"Content chunk had expected size {expected} but Reader supplied {actual} at archive \
path: {}",
format_path_for_error(path)
)]
ContentChunkSizeMismatch { expected: u64, actual: u64, path: Vec<u8> },
#[error("Missing directory chunk index entry")]
MissingDirectoryChunkIndexEntry,
#[error("Missing directory names chunk index entry")]
MissingDirectoryNamesChunkIndexEntry,
#[error("Serialize index")]
SerializeIndex(#[source] std::io::Error),
#[error("Serialize directory chunk index entry")]
SerializeDirectoryChunkIndexEntry(#[source] std::io::Error),
#[error("Serialize directory names chunk index entry")]
SerializeDirectoryNamesChunkIndexEntry(#[source] std::io::Error),
#[error("Serialize directory entry")]
SerializeDirectoryEntry(#[source] std::io::Error),
#[error("Invalid magic bytes, expected [c8, bf, 0b, 48, ad, ab, c5, 11], found {0:02x?}")]
InvalidMagic([u8; 8]),
#[error("Bad length of index entries, expected multiple of {}, found {0}", INDEX_ENTRY_LEN)]
InvalidIndexEntriesLen(u64),
#[error(
"Index entries not strictly increasing, {} followed by {}",
ascii_chunk(prev),
ascii_chunk(next)
)]
IndexEntriesOutOfOrder { prev: ChunkType, next: ChunkType },
#[error(
"Invalid chunk offset for chunk {}, expected {expected}, actual {actual}",
ascii_chunk(chunk_type)
)]
InvalidChunkOffset { chunk_type: ChunkType, expected: u64, actual: u64 },
#[error(
"Invalid chunk length for chunk {}, offset + length overflows, offset: {offset}, length: \
{length}",
ascii_chunk(chunk_type)
)]
InvalidChunkLength { chunk_type: ChunkType, offset: u64, length: u64 },
#[error("Bad length of directory names chunk, expected multiple of 8, found {0}")]
InvalidDirectoryNamesChunkLen(u64),
#[error(
"Bad length of directory chunk, expected multiple of {}, found {0}",
DIRECTORY_ENTRY_LEN
)]
InvalidDirectoryChunkLen(u64),
#[error(
"Unsorted directory entry {entry_index} has name {} but comes after name \
{}",
format_path_for_error(name),
format_path_for_error(previous_name)
)]
DirectoryEntriesOutOfOrder { entry_index: usize, name: Vec<u8>, previous_name: Vec<u8> },
#[error("Directory entry has a zero length name")]
ZeroLengthName,
#[error("Directory entry name starts with '/' {}", format_path_for_error(.0))]
NameStartsWithSlash(Vec<u8>),
#[error("Directory entry name ends with '/' {}", format_path_for_error(.0))]
NameEndsWithSlash(Vec<u8>),
#[error("Directory entry name contains the null character {}", format_path_for_error(.0))]
NameContainsNull(Vec<u8>),
#[error(
"Directory entry name contains an empty segment (consecutive '/') {}",
format_path_for_error(.0)
)]
NameContainsEmptySegment(Vec<u8>),
#[error("Directory entry name contains a segment of '.' {}", format_path_for_error(.0))]
NameContainsDotSegment(Vec<u8>),
#[error("Directory entry name contains a segment of '..' {}", format_path_for_error(.0))]
NameContainsDotDotSegment(Vec<u8>),
#[error(
"Path data for directory entry {entry_index} has offset {offset} larger than \
directory names chunk size {chunk_size}"
)]
PathDataOffsetTooLarge { entry_index: usize, offset: usize, chunk_size: usize },
#[error(
"Path data for directory entry {entry_index} has offset {offset} plus length \
{length} larger than directory names chunk size {chunk_size}"
)]
PathDataLengthTooLarge { entry_index: usize, offset: usize, length: u16, chunk_size: usize },
#[error("Path data not utf8: {path:?}")]
PathDataInvalidUtf8 { source: std::str::Utf8Error, path: Vec<u8> },
#[error("Path not present in archive: {}", format_path_for_error(.0))]
PathNotPresent(Vec<u8>),
#[error("Attempted to read past the end of a content chunk")]
ReadPastEnd,
#[error("Content chunk offset larger than u64::MAX")]
ContentChunkOffsetOverflow,
#[error(
"Directory entry for {} has a bad content chunk offset, expected {expected} actual \
{actual}",
format_path_for_error(name)
)]
InvalidContentChunkOffset { name: Vec<u8>, expected: u64, actual: u64 },
#[error(
"Directory entry for {} implies a content chunk end that overflows u64, offset: \
{offset}, length: {length}",
format_path_for_error(name)
)]
ContentChunkEndOverflow { name: Vec<u8>, offset: u64, length: u64 },
#[error(
"Archive has {archive_size} bytes, but content chunk (including padding) for {} ends \
at {lower_bound}",
format_path_for_error(name)
)]
ContentChunkBeyondArchive { name: Vec<u8>, lower_bound: u64, archive_size: u64 },
#[error(
"The content chunk for {} is {chunk_size} bytes but the system does not support \
buffers larger than {} bytes",
format_path_for_error(name),
usize::MAX
)]
ContentChunkDoesNotFitInMemory { name: Vec<u8>, chunk_size: u64 },
}
fn ascii_chunk(bytes: &[u8; 8]) -> String {
let v: Vec<u8> = bytes.iter().copied().flat_map(std::ascii::escape_default).collect();
String::from_utf8_lossy(&v).into_owned()
}
fn format_path_for_error(path: &[u8]) -> String {
std::str::from_utf8(path).map(|s| format!("{s:?}")).unwrap_or_else(|_| format!("{path:?}"))
}
#[cfg(test)]
mod tests {
#[test]
fn ascii_chunk() {
assert_eq!(
super::ascii_chunk(&[b'A', 0, b'b', b'\\', 255, b'-', b'_', b'\n']),
r"A\x00b\\\xff-_\n"
);
}
}