fritzdecode 0.1.0

Tools for working with AVM FritzBox config file exports
Documentation
use std::fmt;

use thiserror::Error;

use crate::SpecialLine;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StringOrEof {
    String(String),
    Eof,
}

pub(crate) const EOF: StringOrEof = StringOrEof::Eof;

impl fmt::Display for StringOrEof {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StringOrEof::String(s) => write!(f, "{s:?}"),
            StringOrEof::Eof => write!(f, "end of file"),
        }
    }
}

impl From<String> for StringOrEof {
    fn from(s: String) -> Self {
        Self::String(s)
    }
}

#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormatError {
    #[error("Expected header line, got {got:?}")]
    ExpectedHeader { got: StringOrEof },
    #[error("Expected \"end of export\" line, got {got:?}")]
    ExpectedEndOfExport { got: StringOrEof },
    #[error("Expected property, got {got:?}")]
    ExpectedProperty { got: StringOrEof },
    #[error("Invalid special line")]
    InvalidSpecialLine {
        line: String,
        kind: SpecialLineError,
    },
    #[error("Expected special line")]
    ExpectedSpecialLine,
    #[error("Expected end of input")]
    ExpectedEndOfInput,
    #[error("Invalid secret prefix, expected `$$$$`: {secret}")]
    InvalidSecretPrefix { secret: String },
    #[error("Missing `Password` property")]
    MissingPasswordProperty,
    #[error("Invalid length of encryption key: expected 32 bytes, got {length}")]
    InvalidEncryptionKeyLength { length: usize },
    #[error("Invalid CRC: expected `{expected:08X}`, calculated `{calculated:08X}`")]
    InvalidCrc { expected: u32, calculated: u32 },
    #[error("Expected start of file, got {got:?}")]
    ExpectedStartOfFileSection { got: StringOrEof },
    #[error("Expected end of file section, got {got:?}")]
    ExpectedEndOfFileSection { got: SpecialLine },
    #[error("Invalid binary data")]
    InvalidBinaryData(data_encoding::DecodeError),
    #[error("Decrypted secret is not valid UTF-8")]
    SecretNotUtf8(#[source] std::str::Utf8Error),
}

#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum SpecialLineError {
    #[error("Not a special line (must start with `**** `)")]
    NotASpecialLine,
    #[error("Malformed CRC: {crc:?}")]
    MalformedCrc { crc: String },
    #[error("Unknown format")]
    UnknownFormat,
}

#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum DecryptionError {
    #[error(
        "Bad ciphertext length: expected up to 4 excess bytes from base32 decoding, got {excess}"
    )]
    BadCiphertextLength { excess: usize },
    #[error(
        "Hash mismatch: should be `{expected}...`, got `{found}`",
        expected = hex::encode(.expected),
        found = hex::encode(.found),
    )]
    HashMismatch { expected: [u8; 4], found: [u8; 16] },
    #[error(
        "Missing null terminator in decrypted string value: {} ({})",
        hex::encode(data),
        String::from_utf8_lossy(data)
    )]
    MissingNullTerminator { data: Vec<u8> },
}

#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum EncryptionError {
    #[error("Data too large: {length}")]
    DataTooLarge { length: usize },
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    #[error("Encoding error")]
    EncodingError(#[from] data_encoding::DecodeError),
    #[error("Format error")]
    FormatError(#[from] FormatError),
    #[error("Decryption error")]
    Decryption(#[from] DecryptionError),
    #[error("Encryption error")]
    Encryption(#[from] EncryptionError),
    #[error("IO error")]
    Io(#[from] std::io::Error),
}