use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Invalid MPQ format: {0}")]
InvalidFormat(String),
#[error("Unsupported MPQ version: {0}")]
UnsupportedVersion(u16),
#[error("File not found: {0}")]
FileNotFound(String),
#[error("File already exists: {0}")]
FileExists(String),
#[error("Hash table error: {0}")]
HashTable(String),
#[error("Block table error: {0}")]
BlockTable(String),
#[error("Cryptography error: {0}")]
Crypto(String),
#[error("Compression error: {0}")]
Compression(String),
#[error("Signature verification failed: {0}")]
SignatureVerification(String),
#[error("Invalid header: {0}")]
InvalidHeader(String),
#[error("Archive is read-only")]
ReadOnly,
#[error("Operation not supported for MPQ version {version}: {operation}")]
OperationNotSupported {
version: u16,
operation: String,
},
#[error("Invalid file size: expected {expected}, got {actual}")]
InvalidFileSize {
expected: u64,
actual: u64,
},
#[error("Memory mapping error: {0}")]
MemoryMap(String),
#[error("Invalid UTF-8 in filename")]
InvalidUtf8,
#[error("Archive capacity exceeded: {0}")]
CapacityExceeded(String),
#[error("Checksum mismatch for {file}: expected {expected:08x}, got {actual:08x}")]
ChecksumMismatch {
file: String,
expected: u32,
actual: u32,
},
#[error("MD5 hash mismatch for {table}")]
MD5Mismatch {
table: String,
},
#[error("Not implemented: {0}")]
NotImplemented(&'static str),
#[error("Security validation failed: {0}")]
SecurityViolation(String),
#[error("Malicious content detected: {0}")]
MaliciousContent(String),
#[error("Resource exhaustion attempt: {0}")]
ResourceExhaustion(String),
#[error("Directory traversal attempt: {0}")]
DirectoryTraversal(String),
#[error("Compression bomb detected: ratio {ratio}:1 exceeds limit of {limit}:1")]
CompressionBomb {
ratio: u64,
limit: u64,
},
#[error("Invalid bounds access: {0}")]
InvalidBounds(String),
#[error("Unsupported feature: {0}")]
UnsupportedFeature(String),
#[error("Decompression error: {0}")]
Decompression(String),
}
impl Error {
pub fn invalid_format<S: Into<String>>(msg: S) -> Self {
Error::InvalidFormat(msg.into())
}
pub fn crypto<S: Into<String>>(msg: S) -> Self {
Error::Crypto(msg.into())
}
pub fn compression<S: Into<String>>(msg: S) -> Self {
Error::Compression(msg.into())
}
pub fn hash_table<S: Into<String>>(msg: S) -> Self {
Error::HashTable(msg.into())
}
pub fn block_table<S: Into<String>>(msg: S) -> Self {
Error::BlockTable(msg.into())
}
pub fn security_violation<S: Into<String>>(msg: S) -> Self {
Error::SecurityViolation(msg.into())
}
pub fn malicious_content<S: Into<String>>(msg: S) -> Self {
Error::MaliciousContent(msg.into())
}
pub fn resource_exhaustion<S: Into<String>>(msg: S) -> Self {
Error::ResourceExhaustion(msg.into())
}
pub fn directory_traversal<S: Into<String>>(msg: S) -> Self {
Error::DirectoryTraversal(msg.into())
}
pub fn compression_bomb(ratio: u64, limit: u64) -> Self {
Error::CompressionBomb { ratio, limit }
}
pub fn invalid_bounds<S: Into<String>>(msg: S) -> Self {
Error::InvalidBounds(msg.into())
}
pub fn unsupported_feature<S: Into<String>>(msg: S) -> Self {
Error::UnsupportedFeature(msg.into())
}
pub fn io_error<S: Into<String>>(msg: S) -> Self {
Error::Io(io::Error::other(msg.into()))
}
pub fn decompression<S: Into<String>>(msg: S) -> Self {
Error::Decompression(msg.into())
}
pub fn is_corruption(&self) -> bool {
matches!(
self,
Error::InvalidFormat(_)
| Error::ChecksumMismatch { .. }
| Error::MD5Mismatch { .. }
| Error::SignatureVerification(_)
| Error::InvalidHeader(_)
)
}
pub fn is_security_threat(&self) -> bool {
matches!(
self,
Error::SecurityViolation(_)
| Error::MaliciousContent(_)
| Error::ResourceExhaustion(_)
| Error::DirectoryTraversal(_)
| Error::CompressionBomb { .. }
)
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Error::FileNotFound(_) | Error::ReadOnly | Error::OperationNotSupported { .. }
) && !self.is_security_threat() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = Error::invalid_format("bad header");
assert_eq!(err.to_string(), "Invalid MPQ format: bad header");
let err = Error::FileNotFound("test.txt".to_string());
assert_eq!(err.to_string(), "File not found: test.txt");
}
#[test]
fn test_error_classification() {
let corruption_err = Error::ChecksumMismatch {
file: "test".to_string(),
expected: 0x12345678,
actual: 0x87654321,
};
assert!(corruption_err.is_corruption());
assert!(!corruption_err.is_recoverable());
let recoverable_err = Error::FileNotFound("missing.txt".to_string());
assert!(!recoverable_err.is_corruption());
assert!(recoverable_err.is_recoverable());
}
#[test]
fn test_memory_mapping_errors() {
let err = Error::unsupported_feature("Memory mapping not available");
assert_eq!(
err.to_string(),
"Unsupported feature: Memory mapping not available"
);
let err = Error::invalid_bounds("Read beyond file end");
assert_eq!(
err.to_string(),
"Invalid bounds access: Read beyond file end"
);
}
}