bitbottle 0.10.0

a modern archive file format
Documentation
use cfg_if::cfg_if;
use std::path::PathBuf;
use thiserror::Error;
use crate::bottle_cap::BottleType;


cfg_if! {
    if #[cfg(feature = "argonautica")] {
        type ArgonLibraryError = argonautica::Error;
    } else {
        type ArgonLibraryError = argon2::Error;
    }
}

cfg_if! {
    if #[cfg(feature = "lzma2")] {
        type LzmaLibraryError = lzma::LzmaError;
    } else {
        type LzmaLibraryError = String;
    }
}


#[derive(Error, Debug)]
#[non_exhaustive]
pub enum BottleError {
    #[error("I/O error: {:?}{}",
        .0.kind(),
        .0.get_ref().map(|inner| format!(": {inner}")).unwrap_or_else(|| "".to_string())
    )]
    IoError(#[from] std::io::Error),

    #[error("{0}: {1:?}")]
    FileError(PathBuf, std::io::ErrorKind),

    #[error("Bad magic number")]
    BadMagic,

    #[error("Unknown bottle version")]
    UnknownVersion,

    #[error("Unknown bottle type")]
    UnknownBottleType,

    #[error("Bottle header too large")]
    HeaderTooLarge,

    #[error("Bad CRC32C: got {got:x}, expected {expected:x}")]
    BadCrc { expected: u32, got: u32 },

    #[error("Corrupt stream (bad signal)")]
    CorruptStream,

    #[error("Internal error: previous stream was not closed")]
    InvalidBottleState,

    #[error("Wrong bottle type: got {got:?}, expected {expected:?}")]
    WrongBottleType { expected: BottleType, got: BottleType },

    #[error("Corrupt bottle: incorrect stream type(s) for bottle")]
    WrongStreamType,

    #[error("Extra stream in bottle")]
    UnexpectedStream,

    #[error("Bottle is missing a mandatory header")]
    MissingHeader,

    #[error("Internal error: No stream has been opened in this bottle")]
    NoStream,


    // ----- signatures:

    #[error("Not an Ed25519 key")]
    NotAnEd25519Key,

    #[error("Error decoding ssh key format")]
    SshEncodingError,

    #[error("Can't decode ssh key on line {line_no}")]
    SshKeyFormatError { path: PathBuf, line_no: usize },

    #[error("Not an ssh key file: {0}")]
    InvalidSshFile(PathBuf),

    #[error("Unsupported encryption ({kind}) in ssh file: {path}")]
    UnsupportedSshFileEncryption { path: PathBuf, kind: String },

    #[error("Password required for this SSH key file")]
    SshPasswordRequired,

    #[error("Error encrypting message with Ed25519 key material")]
    DryocBoxError,

    #[error("Error signing message with Ed25519 key material")]
    DryocSignError,


    // ----- signed bottles:

    #[error("Unknown signature algorithm")]
    UnknownSignatureAlgorithm,

    #[error("No public key provided to verify signature")]
    NoSignaturePublicKey,

    #[error("Bad signature")]
    BadSignature,

    #[error("No acceptable signature from allowed public keys")]
    NoAcceptableSignature,

    #[error("Key commitment is missing (old archive? try --old)")]
    NoKeyCommitment,

    #[error("Generated key is incorrect (possibly shenanigans)")]
    BadKeyCommitment,


    // ----- compressed bottles:

    #[error("Unknown compression")]
    UnknownCompression,

    #[error("Error compressing data")]
    CompressionError,  // probably an internal error in the compressor

    #[error("LZMA error: {0}")]
    Lzma2Error(LzmaLibraryError),

    #[error("LZMA not supported (built without feature=lzma2)")]
    LzmaFeatureMissing,


    // ----- encrypted bottles:

    #[error("No CPRNG random bytes available")]
    NoRandom,

    #[error("Unknown encryption")]
    UnknownEncryption,

    #[error("Bad key size for encryption algorithm")]
    BadKeySize,

    #[error("No key or password provided for encrypted bottle")]
    RequiresKey,

    #[error("Password provided but encrypted bottle is not password protected")]
    NotPasswordEncrypted,

    #[error("Internal error: Argon2 key generation error: {0}")]
    Argon2Error(ArgonLibraryError),  // probably an internal error

    #[error("Public key in encrypted bottle is corrupted")]
    CorruptPublicKey,

    #[error("No public key that can decode this bottle (encrypted for: {})", .0.join(", "))]
    NoMatchingPublicKey(Vec<String>),

    #[error("Encryption engine failure (wrong password or key?)")]
    CipherError,


    // ----- file bottles:

    #[error("File archive is missing some blocks (corrupted)")]
    IncompleteFileArchive,

    #[error("Unknown hash type for file blocking")]
    UnknownHashType,

    #[error("Invalid path for adding to archive (contains . or ..): {0:?}")]
    InvalidAddPath(PathBuf),

    #[error("Invalid path (failed to parse: OS error?)")]
    BadPath,

    #[error("Paths would resolve to the same relative path in the archive: {0:?}, {1:?}")]
    DuplicatePaths(PathBuf, PathBuf),
}

impl BottleError {
    pub fn to_io_error(self) -> std::io::Error {
        std::io::Error::other(Box::new(self))
    }
}

pub type BottleResult<T> = Result<T, BottleError>;