1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
//! # Dvdcss
//!
//! The `dvdcss` crate contains bindings to the C library `libdvdcss`, providing a simple and
//! easy way to read content from encrypted DVDs.

use std::ffi::CStr;
use std::os::raw::c_void;

mod ffi;

#[repr(i32)]
/// Describes whether to recalculate keys.
pub enum SeekOptions {
    None = 0,
    /// At the discretion of libdvdcss, checks the title key and allows for it to be recalculated.
    Mpeg = 1 << 0,
    /// Checks the title key and allows for it to be recalculated.
    Key = 1 << 1,
}

/// Information may only be loaded out of a DVD in 2048 byte increments.
pub type Block = [u8; 2048];

#[repr(transparent)]
/// An instance of dvdcss. Represents a DVD which has been loaded into a drive.
///
/// Must have its destructor run.
pub struct DVD {
    ptr: ffi::dvdcss_t,
}

impl DVD {
    /// Opens the DVD at the device location specified in the string (eg /dev/sr0).
    ///
    /// Returns `None` if there is no DVD in the drive.
    pub fn new(location: &CStr) -> Option<Self> {
        let ptr = unsafe { ffi::dvdcss_open(location.as_ptr()) };

        match !ptr.is_null() {
            true => Some(Self { ptr }),
            false => None,
        }
    }

    /// Sets the absolute position of the needle of the DVD.
    ///
    /// The value `blocks` determines in intervals of 2048 (the size of a block) the absolute
    /// location to which the needle should be moved.
    ///
    /// The `flags` behavior is documented in `SeekOptions`.
    ///
    /// Returns `Ok` and the new position in blocks if the operation succeeded, and
    /// `Err` if it did not.
    pub fn seek(&self, blocks: u32, flags: SeekOptions) -> Result<u32, ()> {
        let val = unsafe { ffi::dvdcss_seek(self.ptr, blocks as i32, flags as i32) };
        match val >= 0 {
            true => Ok(val as u32),
            false => Err(()),
        }
    }

    /// Fills each `Block` of `buffer` with the raw contents of the DVD from the current
    /// position of the needle, which is modifiable with the `seek` function.
    ///
    /// The `decrypt` boolean should be enabled when the DVD is encrypted.
    ///
    /// Returns `Ok` and the accompanying number of blocks read if the operation
    /// succeeded, and `Err` if it did not.
    pub fn read(&self, buffer: &mut [Block], decrypt: bool) -> Result<u32, ()> {
        let val = unsafe {
            ffi::dvdcss_read(
                self.ptr,
                buffer.as_mut_ptr() as *mut c_void,
                buffer.len() as i32,
                // if decrypt == 0, uses empty flag 0, else uses set flag 1; this is valid
                decrypt as i32,
            )
        };
        match val >= 0 {
            true => Ok(val as u32),
            false => Err(()),
        }
    }

    /// Returns whether the DVD being read is scrambled.
    pub fn is_scrambled(&self) -> bool {
        match unsafe { ffi::dvdcss_is_scrambled(self.ptr) } {
            0 => false,
            1 => true,
            _ => unreachable!(),
        }
    }

    /// Returns text describing the latest error encountered by dvdcss.
    ///
    /// Output is undefined if no error was encountered, but should not crash to read.
    pub fn latest_error(&self) -> &CStr {
        unsafe { CStr::from_ptr(ffi::dvdcss_error(self.ptr)) }
    }
}

impl Drop for DVD {
    fn drop(&mut self) {
        let val = unsafe { ffi::dvdcss_close(self.ptr) };
        assert_eq!(val, 0);
    }
}