libfreemkv 0.8.2

Open source raw disc access library for optical drives
Documentation
//! Error types for libfreemkv.
//!
//! Every error is a code with structured data. No English text.
//! Applications map codes to localized messages.
//!
//! # Error Code Ranges
//!
//! | Range | Category |
//! |-------|----------|
//! | E1xxx | Device errors |
//! | E2xxx | Profile errors |
//! | E3xxx | Unlock errors |
//! | E4xxx | SCSI errors |
//! | E5xxx | I/O errors |
//! | E6xxx | Disc format errors |
//! | E7xxx | AACS errors |
//! | E8xxx | Keydb errors |
//! | E9xxx | Mux errors |

// ── Error codes ─────────────────────────────────────────────────────────────

pub const E_DEVICE_NOT_FOUND: u16 = 1000;
pub const E_DEVICE_PERMISSION: u16 = 1001;
pub const E_UNSUPPORTED_DRIVE: u16 = 2000;
pub const E_PROFILE_NOT_FOUND: u16 = 2001;
pub const E_PROFILE_PARSE: u16 = 2002;
pub const E_UNLOCK_FAILED: u16 = 3000;
pub const E_SIGNATURE_MISMATCH: u16 = 3001;
pub const E_NOT_UNLOCKED: u16 = 3002;
pub const E_NOT_CALIBRATED: u16 = 3003;
pub const E_SCSI_ERROR: u16 = 4000;
pub const E_SCSI_TIMEOUT: u16 = 4001;
pub const E_IO_ERROR: u16 = 5000;
pub const E_WRITE_ERROR: u16 = 5001;
// Disc format (6xxx)
pub const E_DISC_READ: u16 = 6000;
pub const E_MPLS_PARSE: u16 = 6001;
pub const E_CLPI_PARSE: u16 = 6002;
pub const E_UDF_NOT_FOUND: u16 = 6003;
pub const E_DISC_NO_TITLES: u16 = 6004;
pub const E_DISC_TITLE_RANGE: u16 = 6005;
pub const E_DISC_NO_EXTENTS: u16 = 6006;
pub const E_IFO_PARSE: u16 = 6007;
// AACS (7xxx)
pub const E_AACS_NO_KEYS: u16 = 7000;
pub const E_AACS_CERT_SHORT: u16 = 7001;
pub const E_AACS_AGID_ALLOC: u16 = 7002;
pub const E_AACS_CERT_REJECTED: u16 = 7003;
pub const E_AACS_CERT_READ: u16 = 7004;
pub const E_AACS_CERT_VERIFY: u16 = 7005;
pub const E_AACS_KEY_READ: u16 = 7006;
pub const E_AACS_KEY_REJECTED: u16 = 7007;
pub const E_AACS_KEY_VERIFY: u16 = 7008;
pub const E_AACS_VID_READ: u16 = 7009;
pub const E_AACS_VID_MAC: u16 = 7010;
pub const E_AACS_DATA_KEY: u16 = 7011;
pub const E_AACS_VUK_DERIVE: u16 = 7012;
// Keydb (8xxx)
pub const E_KEYDB_CONNECT: u16 = 8000;
pub const E_KEYDB_HTTP: u16 = 8001;
pub const E_KEYDB_INVALID: u16 = 8002;
pub const E_KEYDB_WRITE: u16 = 8003;
pub const E_KEYDB_PARSE: u16 = 8004;
pub const E_KEYDB_LOAD: u16 = 8005;
// Mux (9xxx)
pub const E_MUX_LOOKAHEAD: u16 = 9000;
pub const E_MUX_WRITE: u16 = 9001;

// ── Error enum ──────────────────────────────────────────────────────────────

/// Structured error with numeric code and context data. No English text.
#[derive(Debug)]
pub enum Error {
    /// Device not found at the given path.
    DeviceNotFound {
        path: String,
    },
    /// Insufficient permissions to open the device.
    DevicePermission {
        path: String,
    },

    /// Drive model is not in the profile database.
    UnsupportedDrive {
        vendor_id: String,
        product_id: String,
        product_revision: String,
    },
    /// No matching firmware profile found for this drive revision.
    ProfileNotFound {
        vendor_id: String,
        product_revision: String,
        vendor_specific: String,
    },
    /// Failed to parse the bundled profile database.
    ProfileParse,

    /// Drive unlock (firmware upload) failed.
    UnlockFailed,
    /// Firmware signature verification failed.
    SignatureMismatch {
        expected: [u8; 4],
        got: [u8; 4],
    },
    /// Operation requires an unlocked drive.
    NotUnlocked,
    /// Operation requires a calibrated drive.
    NotCalibrated,

    /// SCSI command returned an error status.
    ScsiError {
        opcode: u8,
        status: u8,
        sense_key: u8,
    },
    /// SCSI command timed out.
    ScsiTimeout {
        opcode: u8,
    },

    /// Underlying I/O error.
    IoError {
        source: std::io::Error,
    },
    /// Write operation failed.
    WriteError,

    /// Failed to read disc sector.
    DiscRead {
        sector: u64,
    },
    /// MPLS playlist parsing failed.
    MplsParse,
    /// CLPI clip info parsing failed.
    ClpiParse,
    /// File not found on the UDF filesystem.
    UdfNotFound {
        path: String,
    },
    /// Disc contains no playable titles.
    DiscNoTitles,
    /// Title index out of range.
    DiscTitleRange {
        index: usize,
        count: usize,
    },
    /// Title has no sector extents to read.
    DiscNoExtents,
    /// DVD IFO file parsing failed.
    IfoParse,

    /// No AACS decryption keys available for this disc.
    AacsNoKeys,
    /// Host certificate too short.
    AacsCertShort,
    /// Failed to allocate AGID for AACS handshake.
    AacsAgidAlloc,
    /// Drive rejected the host certificate.
    AacsCertRejected,
    /// Failed to read drive certificate.
    AacsCertRead,
    /// Drive certificate verification failed.
    AacsCertVerify,
    /// Failed to read host key from drive.
    AacsKeyRead,
    /// Drive rejected the host key.
    AacsKeyRejected,
    /// Host key verification failed.
    AacsKeyVerify,
    /// Failed to read Volume ID.
    AacsVidRead,
    /// Volume ID MAC verification failed.
    AacsVidMac,
    /// Failed to derive the data key.
    AacsDataKey,
    /// Failed to derive the Volume Unique Key.
    AacsVukDerive,

    /// Failed to connect to the KEYDB server.
    KeydbConnect {
        host: String,
    },
    /// KEYDB server returned an HTTP error.
    KeydbHttp {
        status: u16,
    },
    /// Downloaded KEYDB file is invalid (no entries found).
    KeydbInvalid,
    /// Failed to write KEYDB to disk.
    KeydbWrite {
        path: String,
    },
    /// Failed to parse KEYDB file.
    KeydbParse,
    /// Failed to load KEYDB from disk.
    KeydbLoad {
        path: String,
    },

    /// Lookahead buffer exhausted before codec headers found.
    MuxLookahead,
    /// Muxer write failed.
    MuxWrite,
}

impl Error {
    pub fn code(&self) -> u16 {
        match self {
            Error::DeviceNotFound { .. } => E_DEVICE_NOT_FOUND,
            Error::DevicePermission { .. } => E_DEVICE_PERMISSION,
            Error::UnsupportedDrive { .. } => E_UNSUPPORTED_DRIVE,
            Error::ProfileNotFound { .. } => E_PROFILE_NOT_FOUND,
            Error::ProfileParse => E_PROFILE_PARSE,
            Error::UnlockFailed => E_UNLOCK_FAILED,
            Error::SignatureMismatch { .. } => E_SIGNATURE_MISMATCH,
            Error::NotUnlocked => E_NOT_UNLOCKED,
            Error::NotCalibrated => E_NOT_CALIBRATED,
            Error::ScsiError { .. } => E_SCSI_ERROR,
            Error::ScsiTimeout { .. } => E_SCSI_TIMEOUT,
            Error::IoError { .. } => E_IO_ERROR,
            Error::WriteError => E_WRITE_ERROR,
            Error::DiscRead { .. } => E_DISC_READ,
            Error::MplsParse => E_MPLS_PARSE,
            Error::ClpiParse => E_CLPI_PARSE,
            Error::UdfNotFound { .. } => E_UDF_NOT_FOUND,
            Error::DiscNoTitles => E_DISC_NO_TITLES,
            Error::DiscTitleRange { .. } => E_DISC_TITLE_RANGE,
            Error::DiscNoExtents => E_DISC_NO_EXTENTS,
            Error::IfoParse => E_IFO_PARSE,
            Error::AacsNoKeys => E_AACS_NO_KEYS,
            Error::AacsCertShort => E_AACS_CERT_SHORT,
            Error::AacsAgidAlloc => E_AACS_AGID_ALLOC,
            Error::AacsCertRejected => E_AACS_CERT_REJECTED,
            Error::AacsCertRead => E_AACS_CERT_READ,
            Error::AacsCertVerify => E_AACS_CERT_VERIFY,
            Error::AacsKeyRead => E_AACS_KEY_READ,
            Error::AacsKeyRejected => E_AACS_KEY_REJECTED,
            Error::AacsKeyVerify => E_AACS_KEY_VERIFY,
            Error::AacsVidRead => E_AACS_VID_READ,
            Error::AacsVidMac => E_AACS_VID_MAC,
            Error::AacsDataKey => E_AACS_DATA_KEY,
            Error::AacsVukDerive => E_AACS_VUK_DERIVE,
            Error::KeydbConnect { .. } => E_KEYDB_CONNECT,
            Error::KeydbHttp { .. } => E_KEYDB_HTTP,
            Error::KeydbInvalid => E_KEYDB_INVALID,
            Error::KeydbWrite { .. } => E_KEYDB_WRITE,
            Error::KeydbParse => E_KEYDB_PARSE,
            Error::KeydbLoad { .. } => E_KEYDB_LOAD,
            Error::MuxLookahead => E_MUX_LOOKAHEAD,
            Error::MuxWrite => E_MUX_WRITE,
        }
    }
}

/// Display: "E{code}" with structured data. No English words.
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::DeviceNotFound { path } => write!(f, "E{}: {}", self.code(), path),
            Error::DevicePermission { path } => write!(f, "E{}: {}", self.code(), path),
            Error::UnsupportedDrive {
                vendor_id,
                product_id,
                product_revision,
            } => write!(
                f,
                "E{}: {} {} {}",
                self.code(),
                vendor_id.trim(),
                product_id.trim(),
                product_revision.trim()
            ),
            Error::ProfileNotFound {
                vendor_id,
                product_revision,
                vendor_specific,
            } => write!(
                f,
                "E{}: {} {} {}",
                self.code(),
                vendor_id.trim(),
                product_revision.trim(),
                vendor_specific.trim()
            ),
            Error::SignatureMismatch { expected, got } => write!(
                f,
                "E{}: {:02x}{:02x}{:02x}{:02x}!={:02x}{:02x}{:02x}{:02x}",
                self.code(),
                expected[0],
                expected[1],
                expected[2],
                expected[3],
                got[0],
                got[1],
                got[2],
                got[3]
            ),
            Error::ScsiError {
                opcode,
                status,
                sense_key,
            } => write!(
                f,
                "E{}: 0x{:02x}/0x{:02x}/0x{:02x}",
                self.code(),
                opcode,
                status,
                sense_key
            ),
            Error::ScsiTimeout { opcode } => write!(f, "E{}: 0x{:02x}", self.code(), opcode),
            Error::IoError { source } => write!(f, "E{}: {}", self.code(), source),
            Error::DiscRead { sector } => write!(f, "E{}: {}", self.code(), sector),
            Error::UdfNotFound { path } => write!(f, "E{}: {}", self.code(), path),
            Error::DiscTitleRange { index, count } => {
                write!(f, "E{}: {}/{}", self.code(), index, count)
            }
            Error::KeydbConnect { host } => write!(f, "E{}: {}", self.code(), host),
            Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status),
            Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path),
            Error::KeydbLoad { path } => write!(f, "E{}: {}", self.code(), path),
            // Simple codes — no extra data
            _ => write!(f, "E{}", self.code()),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::IoError { source } => Some(source),
            _ => None,
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Error::IoError { source: e }
    }
}

/// Convenience alias for `Result<T, Error>`.
pub type Result<T> = std::result::Result<T, Error>;