~ireas/nitrokey-rs

8c57d3ace0215adb3cd5edd0f18e8cc513f65ff1 — Robin Krahl 2 years ago 5da2735 + d878599
Merge branch 'feature/error-refactoring' into next

RFC: https://lists.sr.ht/~ireas/nitrokey-rs-dev/%3C20190117000856.slgb6jwkwd3qu6ey%40localhost%3E
M CHANGELOG.md => CHANGELOG.md +13 -0
@@ 2,6 2,19 @@
- Remove the `test-pro` and `test-storage` features.
- Implement `Display` for `Version`.
- Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants.
- Refactor the error handling code:
  - Implement `std::error::Error` for `CommandError`.
  - Add the `Error` enum and the `Result` typedef.
  - Add the `LibraryError` enum and move the library error variants from
    `CommandError` to `LibraryError`.
  - Add the `CommunicationError` enum and move the communication error variants
    from `CommandError` to `CommunicationError`.
  - Return `Error` instead of `CommandError` in all public functions.
  - Move the `CommandError::RngError` variant to `Error::RandError` and the
    `CommandError::Unknown` variant to `Error::Unknown`.
  - Return `CommunicationError::NotConnected` instead of
    `CommandError::Undefined` from the connect functions.
  - Remove the `CommandError::Undefined` variant.

# v0.3.4 (2019-01-20)
- Fix authentication methods that assumed that `char` is signed.

M Cargo.toml => Cargo.toml +2 -2
@@ 15,8 15,8 @@ license = "MIT"
[dependencies]
libc = "0.2"
nitrokey-sys = "3.4"
rand_core = {version = "0.3", default-features = false}
rand_core = {version = "0.3", default-features = false, features = ["std"] }
rand_os = {version = "0.1"}

[dev-dependencies]
nitrokey-test = {version = "0.1"}
nitrokey-test = {version = "0.2"}

M TODO.md => TODO.md +0 -1
@@ 9,7 9,6 @@
- Clear passwords from memory.
- Find a nicer syntax for the `write_config` test.
- Prevent construction of internal types.
- More specific error checking in the tests.
- Check integer conversions.
- Consider implementing `Into<CommandError>` for `(Device, CommandError)`
- Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware

M src/auth.rs => src/auth.rs +31 -32
@@ 6,10 6,9 @@ use nitrokey_sys;

use crate::config::{Config, RawConfig};
use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::error::Error;
use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData};
use crate::util::{
    generate_password, get_command_result, get_cstring, result_from_string, CommandError,
};
use crate::util::{generate_password, get_command_result, get_cstring, result_from_string};

static TEMPORARY_PASSWORD_LENGTH: usize = 25;



@@ 34,12 33,12 @@ pub trait Authenticate {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, DeviceWrapper, User};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn perform_user_task(device: &User<DeviceWrapper>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let device = match device.authenticate_user("123456") {
    ///     Ok(user) => {


@@ 56,10 55,10 @@ pub trait Authenticate {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`RngError`]: enum.CommandError.html#variant.RngError
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)>
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)>
    where
        Self: Device + Sized;



@@ 80,12 79,12 @@ pub trait Authenticate {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, Admin, DeviceWrapper};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let device = match device.authenticate_admin("123456") {
    ///     Ok(admin) => {


@@ 102,10 101,10 @@ pub trait Authenticate {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`RngError`]: enum.CommandError.html#variant.RngError
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)>
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)>
    where
        Self: Device + Sized;
}


@@ 144,7 143,7 @@ pub struct Admin<T: Device> {
    temp_password: Vec<u8>,
}

fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, CommandError)>
fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>
where
    D: Device,
    A: AuthenticatedDevice<D>,


@@ 162,7 161,7 @@ where
    let temp_password_ptr = temp_password.as_ptr() as *const c_char;
    return match callback(password_ptr, temp_password_ptr) {
        0 => Ok(A::new(device, temp_password)),
        rv => Err((device, CommandError::from(rv))),
        rv => Err((device, Error::from(rv))),
    };
}



@@ 170,7 169,7 @@ fn authenticate_user_wrapper<T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<User<DeviceWrapper>, (DeviceWrapper, CommandError)>
) -> Result<User<DeviceWrapper>, (DeviceWrapper, Error)>
where
    T: Device,
    C: Fn(T) -> DeviceWrapper,


@@ 186,7 185,7 @@ fn authenticate_admin_wrapper<T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, CommandError)>
) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, Error)>
where
    T: Device,
    C: Fn(T) -> DeviceWrapper,


@@ 216,14 215,14 @@ impl<T: Device> Deref for User<T> {
}

impl<T: Device> GenerateOtp for User<T> {
    fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_hotp_code(&self, slot: u8) -> Result<String, Error> {
        unsafe {
            let temp_password_ptr = self.temp_password.as_ptr() as *const c_char;
            return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr));
        }
    }

    fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
        unsafe {
            let temp_password_ptr = self.temp_password.as_ptr() as *const c_char;
            return result_from_string(nitrokey_sys::NK_get_totp_code_PIN(


@@ 272,9 271,9 @@ impl<T: Device> Admin<T> {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, Config};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let config = Config::new(None, None, None, false);
    /// match device.authenticate_admin("12345678") {


@@ 288,8 287,8 @@ impl<T: Device> Admin<T> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    pub fn write_config(&self, config: Config) -> Result<(), CommandError> {
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    pub fn write_config(&self, config: Config) -> Result<(), Error> {
        let raw_config = RawConfig::try_from(config)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_write_config(


@@ 303,7 302,7 @@ impl<T: Device> Admin<T> {
        }
    }

    fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> Result<(), CommandError>
    fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> Result<(), Error>
    where
        C: Fn(RawOtpSlotData, *const c_char) -> c_int,
    {


@@ 314,7 313,7 @@ impl<T: Device> Admin<T> {
}

impl<T: Device> ConfigureOtp for Admin<T> {
    fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError> {
    fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error> {
        self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe {
            nitrokey_sys::NK_write_hotp_slot(
                raw_data.number,


@@ 330,7 329,7 @@ impl<T: Device> ConfigureOtp for Admin<T> {
        })
    }

    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError> {
    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error> {
        self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe {
            nitrokey_sys::NK_write_totp_slot(
                raw_data.number,


@@ 346,12 345,12 @@ impl<T: Device> ConfigureOtp for Admin<T> {
        })
    }

    fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError> {
    fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error> {
        let temp_password_ptr = self.temp_password.as_ptr() as *const c_char;
        unsafe { get_command_result(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) }
    }

    fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError> {
    fn erase_totp_slot(&self, slot: u8) -> Result<(), Error> {
        let temp_password_ptr = self.temp_password.as_ptr() as *const c_char;
        unsafe { get_command_result(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) }
    }


@@ 367,7 366,7 @@ impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
}

impl Authenticate for DeviceWrapper {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
        match self {
            DeviceWrapper::Storage(storage) => {
                authenticate_user_wrapper(storage, DeviceWrapper::Storage, password)


@@ 376,7 375,7 @@ impl Authenticate for DeviceWrapper {
        }
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
        match self {
            DeviceWrapper::Storage(storage) => {
                authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password)


@@ 389,13 388,13 @@ impl Authenticate for DeviceWrapper {
}

impl Authenticate for Pro {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
        })
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })


@@ 403,13 402,13 @@ impl Authenticate for Pro {
}

impl Authenticate for Storage {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
        })
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })

M src/config.rs => src/config.rs +4 -4
@@ 1,4 1,4 @@
use crate::util::CommandError;
use crate::error::{Error, LibraryError};

/// The configuration for a Nitrokey.
#[derive(Clone, Copy, Debug, PartialEq)]


@@ 35,13 35,13 @@ fn config_otp_slot_to_option(value: u8) -> Option<u8> {
    None
}

fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, CommandError> {
fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> {
    match value {
        Some(value) => {
            if value < 3 {
                Ok(value)
            } else {
                Err(CommandError::InvalidSlot)
                Err(LibraryError::InvalidSlot.into())
            }
        }
        None => Ok(255),


@@ 66,7 66,7 @@ impl Config {
}

impl RawConfig {
    pub fn try_from(config: Config) -> Result<RawConfig, CommandError> {
    pub fn try_from(config: Config) -> Result<RawConfig, Error> {
        Ok(RawConfig {
            numlock: option_to_config_otp_slot(config.numlock)?,
            capslock: option_to_config_otp_slot(config.capslock)?,

M src/device.rs => src/device.rs +112 -113
@@ 5,11 5,10 @@ use nitrokey_sys;

use crate::auth::Authenticate;
use crate::config::{Config, RawConfig};
use crate::error::{CommunicationError, Error};
use crate::otp::GenerateOtp;
use crate::pws::GetPasswordSafe;
use crate::util::{
    get_command_result, get_cstring, get_last_error, result_from_string, CommandError,
};
use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string};

/// Available Nitrokey models.
#[derive(Clone, Copy, Debug, PartialEq)]


@@ 64,12 63,12 @@ impl fmt::Display for VolumeMode {
///
/// ```no_run
/// use nitrokey::{Authenticate, DeviceWrapper, User};
/// # use nitrokey::CommandError;
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<DeviceWrapper>) {}
/// fn perform_other_task(device: &DeviceWrapper) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {


@@ 90,12 89,12 @@ impl fmt::Display for VolumeMode {
///
/// ```no_run
/// use nitrokey::{DeviceWrapper, Storage};
/// # use nitrokey::CommandError;
/// # use nitrokey::Error;
///
/// fn perform_common_task(device: &DeviceWrapper) {}
/// fn perform_storage_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::connect()?;
/// perform_common_task(&device);
/// match device {


@@ 128,12 127,12 @@ pub enum DeviceWrapper {
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Pro};
/// # use nitrokey::CommandError;
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<Pro>) {}
/// fn perform_other_task(device: &Pro) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::Pro::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {


@@ 170,12 169,12 @@ pub struct Pro {}
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Storage};
/// # use nitrokey::CommandError;
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<Storage>) {}
/// fn perform_other_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::Storage::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {


@@ 287,16 286,16 @@ pub struct StorageStatus {
///
/// This trait provides the commands that can be executed without authentication and that are
/// present on all supported Nitrokey devices.
pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// Returns the model of the connected Nitrokey device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// println!("Connected to a Nitrokey {}", device.get_model());
    /// #    Ok(())


@@ 310,9 309,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.get_serial_number() {
    ///     Ok(number) => println!("serial no: {}", number),


@@ 321,7 320,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// #     Ok(())
    /// # }
    /// ```
    fn get_serial_number(&self) -> Result<String, CommandError> {
    fn get_serial_number(&self) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) }
    }



@@ 332,9 331,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let count = device.get_user_retry_count();
    /// println!("{} remaining authentication attempts (user)", count);


@@ 352,9 351,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let count = device.get_admin_retry_count();
    /// println!("{} remaining authentication attempts (admin)", count);


@@ 371,9 370,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// println!(
    ///     "Firmware version: {}.{}",


@@ 393,9 392,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// println!(
    ///     "Firmware version: {}.{}",


@@ 414,9 413,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let config = device.get_config()?;
    /// println!("numlock binding:          {:?}", config.numlock);


@@ 426,7 425,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// #     Ok(())
    /// # }
    /// ```
    fn get_config(&self) -> Result<Config, CommandError> {
    fn get_config(&self) -> Result<Config, Error> {
        unsafe {
            let config_ptr = nitrokey_sys::NK_read_config();
            if config_ptr.is_null() {


@@ 450,9 449,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.change_admin_pin("12345678", "12345679") {
    ///     Ok(()) => println!("Updated admin PIN."),


@@ 462,9 461,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
    fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {


@@ 486,9 485,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.change_user_pin("123456", "123457") {
    ///     Ok(()) => println!("Updated admin PIN."),


@@ 498,9 497,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_user_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
    fn change_user_pin(&self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {


@@ 522,9 521,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.unlock_user_pin("12345678", "123456") {
    ///     Ok(()) => println!("Unlocked user PIN."),


@@ 534,9 533,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), CommandError> {
    fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        let user_pin_string = get_cstring(user_pin)?;
        unsafe {


@@ 556,9 555,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.lock() {
    ///     Ok(()) => println!("Locked the Nitrokey device."),


@@ 567,7 566,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// #     Ok(())
    /// # }
    /// ```
    fn lock(&self) -> Result<(), CommandError> {
    fn lock(&self) -> Result<(), Error> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_device()) }
    }



@@ 587,9 586,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.factory_reset("12345678") {
    ///     Ok(()) => println!("Performed a factory reset."),


@@ 600,7 599,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// ```
    ///
    /// [`build_aes_key`]: #method.build_aes_key
    fn factory_reset(&self, admin_pin: &str) -> Result<(), CommandError> {
    fn factory_reset(&self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr())) }
    }


@@ 621,9 620,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.build_aes_key("12345678") {
    ///     Ok(()) => println!("New AES keys have been built."),


@@ 634,7 633,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
    /// ```
    ///
    /// [`factory_reset`]: #method.factory_reset
    fn build_aes_key(&self, admin_pin: &str) -> Result<(), CommandError> {
    fn build_aes_key(&self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr())) }
    }


@@ 645,7 644,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
///
/// # Errors
///
/// - [`Undefined`][] if no Nitrokey device is connected
/// - [`NotConnected`][] if no Nitrokey device is connected
///
/// # Example
///


@@ 660,15 659,15 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp {
/// }
/// ```
///
/// [`Undefined`]: enum.CommandError.html#variant.Undefined
pub fn connect() -> Result<DeviceWrapper, CommandError> {
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
pub fn connect() -> Result<DeviceWrapper, Error> {
    unsafe {
        match nitrokey_sys::NK_login_auto() {
            1 => match get_connected_device() {
                Some(wrapper) => Ok(wrapper),
                None => Err(CommandError::Undefined),
                None => Err(CommunicationError::NotConnected.into()),
            },
            _ => Err(CommandError::Undefined),
            _ => Err(CommunicationError::NotConnected.into()),
        }
    }
}


@@ 677,7 676,7 @@ pub fn connect() -> Result<DeviceWrapper, CommandError> {
///
/// # Errors
///
/// - [`Undefined`][] if no Nitrokey device of the given model is connected
/// - [`NotConnected`][] if no Nitrokey device of the given model is connected
///
/// # Example
///


@@ 693,12 692,12 @@ pub fn connect() -> Result<DeviceWrapper, CommandError> {
/// }
/// ```
///
/// [`Undefined`]: enum.CommandError.html#variant.Undefined
pub fn connect_model(model: Model) -> Result<DeviceWrapper, CommandError> {
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
pub fn connect_model(model: Model) -> Result<DeviceWrapper, Error> {
    if connect_enum(model) {
        Ok(create_device_wrapper(model))
    } else {
        Err(CommandError::Undefined)
        Err(CommunicationError::NotConnected.into())
    }
}



@@ 741,19 740,19 @@ impl DeviceWrapper {
}

impl GenerateOtp for DeviceWrapper {
    fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
    fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
        self.device().get_hotp_slot_name(slot)
    }

    fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
    fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
        self.device().get_totp_slot_name(slot)
    }

    fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_hotp_code(&self, slot: u8) -> Result<String, Error> {
        self.device().get_hotp_code(slot)
    }

    fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
        self.device().get_totp_code(slot)
    }
}


@@ 772,7 771,7 @@ impl Pro {
    ///
    /// # Errors
    ///
    /// - [`Undefined`][] if no Nitrokey device of the given model is connected
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///


@@ 787,12 786,12 @@ impl Pro {
    /// }
    /// ```
    ///
    /// [`Undefined`]: enum.CommandError.html#variant.Undefined
    pub fn connect() -> Result<Pro, CommandError> {
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect() -> Result<Pro, Error> {
        // TODO: maybe Option instead of Result?
        match connect_enum(Model::Pro) {
            true => Ok(Pro {}),
            false => Err(CommandError::Undefined),
            false => Err(CommunicationError::NotConnected.into()),
        }
    }
}


@@ 818,7 817,7 @@ impl Storage {
    ///
    /// # Errors
    ///
    /// - [`Undefined`][] if no Nitrokey device of the given model is connected
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///


@@ 833,12 832,12 @@ impl Storage {
    /// }
    /// ```
    ///
    /// [`Undefined`]: enum.CommandError.html#variant.Undefined
    pub fn connect() -> Result<Storage, CommandError> {
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect() -> Result<Storage, Error> {
        // TODO: maybe Option instead of Result?
        match connect_enum(Model::Storage) {
            true => Ok(Storage {}),
            false => Err(CommandError::Undefined),
            false => Err(CommunicationError::NotConnected.into()),
        }
    }



@@ 856,9 855,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.change_update_pin("12345678", "87654321") {
    ///     Ok(()) => println!("Updated update PIN."),


@@ 868,9 867,9 @@ impl Storage {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
    pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {


@@ 896,9 895,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_firmware_update("12345678") {
    ///     Ok(()) => println!("Nitrokey entered update mode."),


@@ 908,9 907,9 @@ impl Storage {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), CommandError> {
    pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), Error> {
        let update_pin_string = get_cstring(update_pin)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_enable_firmware_update(


@@ 932,9 931,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => println!("Enabled the encrypted volume."),


@@ 944,9 943,9 @@ impl Storage {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), CommandError> {
    pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), Error> {
        let user_pin = get_cstring(user_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) }
    }


@@ 959,11 958,11 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => {


@@ 981,7 980,7 @@ impl Storage {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_encrypted_volume(&self) -> Result<(), CommandError> {
    pub fn disable_encrypted_volume(&self) -> Result<(), Error> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_encrypted_volume()) }
    }



@@ 1007,9 1006,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {


@@ 1022,8 1021,8 @@ impl Storage {
    ///
    /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), CommandError> {
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), Error> {
        let volume_password = get_cstring(volume_password)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_unlock_hidden_volume(


@@ 1040,11 1039,11 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {


@@ 1063,7 1062,7 @@ impl Storage {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_hidden_volume(&self) -> Result<(), CommandError> {
    pub fn disable_hidden_volume(&self) -> Result<(), Error> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_hidden_volume()) }
    }



@@ 1089,9 1088,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?;


@@ 1100,14 1099,14 @@ impl Storage {
    /// ```
    ///
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    pub fn create_hidden_volume(
        &self,
        slot: u8,
        start: u8,
        end: u8,
        password: &str,
    ) -> Result<(), CommandError> {
    ) -> Result<(), Error> {
        let password = get_cstring(password)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_create_hidden_volume(


@@ 1133,10 1132,10 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) {
    ///     Ok(()) => println!("Set the unencrypted volume to read-write mode."),


@@ 1146,13 1145,13 @@ impl Storage {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn set_unencrypted_volume_mode(
        &self,
        admin_pin: &str,
        mode: VolumeMode,
    ) -> Result<(), CommandError> {
    ) -> Result<(), Error> {
        let admin_pin = get_cstring(admin_pin)?;
        let result = match mode {
            VolumeMode::ReadOnly => unsafe {


@@ 1170,11 1169,11 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.get_status() {
    ///     Ok(status) => {


@@ 1185,7 1184,7 @@ impl Storage {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_status(&self) -> Result<StorageStatus, CommandError> {
    pub fn get_status(&self) -> Result<StorageStatus, Error> {
        let mut raw_status = nitrokey_sys::NK_storage_status {
            unencrypted_volume_read_only: false,
            unencrypted_volume_active: false,


@@ 1214,11 1213,11 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.get_production_info() {
    ///     Ok(data) => {


@@ 1230,7 1229,7 @@ impl Storage {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_production_info(&self) -> Result<StorageProductionInfo, CommandError> {
    pub fn get_production_info(&self) -> Result<StorageProductionInfo, Error> {
        let mut raw_data = nitrokey_sys::NK_storage_ProductionTest {
            FirmwareVersion_au8: [0, 2],
            FirmwareVersionInternal_u8: 0,


@@ 1265,9 1264,9 @@ impl Storage {
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.clear_new_sd_card_warning("12345678") {
    ///     Ok(()) => println!("Cleared the new SD card warning."),


@@ 1277,9 1276,9 @@ impl Storage {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), CommandError> {
    pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin = get_cstring(admin_pin)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())


@@ 1287,7 1286,7 @@ impl Storage {
    }

    /// Blinks the red and green LED alternatively and infinitely until the device is reconnected.
    pub fn wink(&self) -> Result<(), CommandError> {
    pub fn wink(&self) -> Result<(), Error> {
        get_command_result(unsafe { nitrokey_sys::NK_wink() })
    }



@@ 1305,9 1304,9 @@ impl Storage {
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn export_firmware(&self, admin_pin: &str) -> Result<(), CommandError> {
    pub fn export_firmware(&self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) })
    }

A src/error.rs => src/error.rs +233 -0
@@ 0,0 1,233 @@
use std::error;
use std::fmt;
use std::os::raw;
use std::result;

/// An error returned by the nitrokey crate.
#[derive(Debug)]
pub enum Error {
    /// An error reported by the Nitrokey device in the response packet.
    CommandError(CommandError),
    /// A device communication.
    CommunicationError(CommunicationError),
    /// A library usage error.
    LibraryError(LibraryError),
    /// An error that occured during random number generation.
    RandError(rand_core::Error),
    /// An error that is caused by an unexpected value returned by libnitrokey.
    UnexpectedError,
    /// An unknown error returned by libnitrokey.
    Unknown(i64),
}

impl From<raw::c_int> for Error {
    fn from(code: raw::c_int) -> Self {
        if let Some(err) = CommandError::try_from(code) {
            Error::CommandError(err)
        } else if let Some(err) = CommunicationError::try_from(256 - code) {
            Error::CommunicationError(err)
        } else if let Some(err) = LibraryError::try_from(code) {
            Error::LibraryError(err)
        } else {
            Error::Unknown(code.into())
        }
    }
}

impl From<CommandError> for Error {
    fn from(err: CommandError) -> Self {
        Error::CommandError(err)
    }
}

impl From<CommunicationError> for Error {
    fn from(err: CommunicationError) -> Self {
        Error::CommunicationError(err)
    }
}

impl From<LibraryError> for Error {
    fn from(err: LibraryError) -> Self {
        Error::LibraryError(err)
    }
}

impl From<rand_core::Error> for Error {
    fn from(error: rand_core::Error) -> Self {
        Error::RandError(error)
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            Error::CommandError(ref err) => Some(err),
            Error::CommunicationError(ref err) => Some(err),
            Error::LibraryError(ref err) => Some(err),
            Error::RandError(ref err) => Some(err),
            Error::UnexpectedError => None,
            Error::Unknown(_) => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::CommandError(ref err) => write!(f, "Command error: {}", err),
            Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err),
            Error::LibraryError(ref err) => write!(f, "Library error: {}", err),
            Error::RandError(ref err) => write!(f, "RNG error: {}", err),
            Error::UnexpectedError => write!(f, "An unexpected error occurred"),
            Error::Unknown(ref err) => write!(f, "Unknown error: {}", err),
        }
    }
}

/// A result returned by the nitrokey crate.
pub type Result<T> = result::Result<T, Error>;

/// An error reported by the Nitrokey device in the response packet.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CommandError {
    /// A packet with a wrong checksum has been sent or received.
    WrongCrc,
    /// A command tried to access an OTP slot that does not exist.
    WrongSlot,
    /// A command tried to generate an OTP on a slot that is not configured.
    SlotNotProgrammed,
    /// The provided password is wrong.
    WrongPassword,
    /// You are not authorized for this command or provided a wrong temporary
    /// password.
    NotAuthorized,
    /// An error occurred when getting or setting the time.
    Timestamp,
    /// You did not provide a name for the OTP slot.
    NoName,
    /// This command is not supported by this device.
    NotSupported,
    /// This command is unknown.
    UnknownCommand,
    /// AES decryption failed.
    AesDecryptionFailed,
}

impl CommandError {
    fn try_from(value: raw::c_int) -> Option<Self> {
        match value {
            1 => Some(CommandError::WrongCrc),
            2 => Some(CommandError::WrongSlot),
            3 => Some(CommandError::SlotNotProgrammed),
            4 => Some(CommandError::WrongPassword),
            5 => Some(CommandError::NotAuthorized),
            6 => Some(CommandError::Timestamp),
            7 => Some(CommandError::NoName),
            8 => Some(CommandError::NotSupported),
            9 => Some(CommandError::UnknownCommand),
            10 => Some(CommandError::AesDecryptionFailed),
            _ => None,
        }
    }
}

impl error::Error for CommandError {}

impl fmt::Display for CommandError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            CommandError::WrongCrc => "A packet with a wrong checksum has been sent or received",
            CommandError::WrongSlot => "The given slot does not exist",
            CommandError::SlotNotProgrammed => "The given slot is not programmed",
            CommandError::WrongPassword => "The given password is wrong",
            CommandError::NotAuthorized => {
                "You are not authorized for this command or provided a wrong temporary \
                 password"
            }
            CommandError::Timestamp => "An error occurred when getting or setting the time",
            CommandError::NoName => "You did not provide a name for the slot",
            CommandError::NotSupported => "This command is not supported by this device",
            CommandError::UnknownCommand => "This command is unknown",
            CommandError::AesDecryptionFailed => "AES decryption failed",
        })
    }
}

/// A device communication error.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CommunicationError {
    /// Could not connect to a Nitrokey device.
    NotConnected,
    /// Sending a packet failed.
    SendingFailure,
    /// Receiving a packet failed.
    ReceivingFailure,
    /// A packet with a wrong checksum was received.
    InvalidCrc,
}

impl CommunicationError {
    fn try_from(value: raw::c_int) -> Option<Self> {
        match value {
            2 => Some(CommunicationError::NotConnected),
            3 => Some(CommunicationError::SendingFailure),
            4 => Some(CommunicationError::ReceivingFailure),
            5 => Some(CommunicationError::InvalidCrc),
            _ => None,
        }
    }
}

impl error::Error for CommunicationError {}

impl fmt::Display for CommunicationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            CommunicationError::NotConnected => "Could not connect to a Nitrokey device",
            CommunicationError::SendingFailure => "Sending a packet failed",
            CommunicationError::ReceivingFailure => "Receiving a packet failed",
            CommunicationError::InvalidCrc => "A packet with a wrong checksum was received",
        })
    }
}

/// A library usage error.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LibraryError {
    /// A supplied string exceeded a length limit.
    StringTooLong,
    /// You passed an invalid slot.
    InvalidSlot,
    /// The supplied string was not in hexadecimal format.
    InvalidHexString,
    /// The target buffer was smaller than the source.
    TargetBufferTooSmall,
    /// You passed a string containing a null byte.
    InvalidString,
}

impl LibraryError {
    fn try_from(value: raw::c_int) -> Option<Self> {
        match value {
            200 => Some(LibraryError::StringTooLong),
            201 => Some(LibraryError::InvalidSlot),
            202 => Some(LibraryError::InvalidHexString),
            203 => Some(LibraryError::TargetBufferTooSmall),
            _ => None,
        }
    }
}

impl error::Error for LibraryError {}

impl fmt::Display for LibraryError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            LibraryError::StringTooLong => "The supplied string is too long",
            LibraryError::InvalidSlot => "The given slot is invalid",
            LibraryError::InvalidHexString => "The supplied string is not in hexadecimal format",
            LibraryError::TargetBufferTooSmall => "The target buffer is too small",
            LibraryError::InvalidString => "You passed a string containing a null byte",
        })
    }
}

M src/lib.rs => src/lib.rs +9 -7
@@ 25,9 25,9 @@
//!
//! ```no_run
//! use nitrokey::Device;
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), CommandError> {
//! # fn try_main() -> Result<(), Error> {
//! let device = nitrokey::connect()?;
//! println!("{}", device.get_serial_number()?);
//! #     Ok(())


@@ 38,9 38,9 @@
//!
//! ```no_run
//! use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), (CommandError)> {
//! # fn try_main() -> Result<(), Error> {
//! let device = nitrokey::connect()?;
//! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
//! match device.authenticate_admin("12345678") {


@@ 60,9 60,9 @@
//!
//! ```no_run
//! use nitrokey::{Device, GenerateOtp};
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), (CommandError)> {
//! # fn try_main() -> Result<(), Error> {
//! let device = nitrokey::connect()?;
//! match device.get_hotp_code(1) {
//!     Ok(code) => println!("Generated HOTP code: {}", code),


@@ 89,6 89,7 @@
mod auth;
mod config;
mod device;
mod error;
mod otp;
mod pws;
mod util;


@@ 103,9 104,10 @@ pub use crate::device::{
    connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage,
    StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError, Result};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::util::{CommandError, LogLevel};
pub use crate::util::LogLevel;

/// The default admin PIN for all Nitrokey devices.
pub const DEFAULT_ADMIN_PIN: &'static str = "12345678";

M src/otp.rs => src/otp.rs +42 -41
@@ 2,7 2,8 @@ use std::ffi::CString;

use nitrokey_sys;

use crate::util::{get_command_result, get_cstring, result_from_string, CommandError};
use crate::error::Error;
use crate::util::{get_command_result, get_cstring, result_from_string};

/// Modes for one-time password generation.
#[derive(Clone, Copy, Debug, PartialEq)]


@@ 28,9 29,9 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
    /// match device.authenticate_admin("12345678") {


@@ 46,10 47,10 @@ pub trait ConfigureOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`NoName`]: enum.CommandError.html#variant.NoName
    fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError>;
    fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error>;

    /// Configure a TOTP slot with the given data and set the TOTP time window to the given value
    /// (default 30).


@@ 64,9 65,9 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits);
    /// match device.authenticate_admin("12345678") {


@@ 82,10 83,10 @@ pub trait ConfigureOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`NoName`]: enum.CommandError.html#variant.NoName
    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError>;
    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error>;

    /// Erases an HOTP slot.
    ///


@@ 97,9 98,9 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(admin) => {


@@ 114,8 115,8 @@ pub trait ConfigureOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError>;
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error>;

    /// Erases a TOTP slot.
    ///


@@ 127,9 128,9 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(admin) => {


@@ 144,8 145,8 @@ pub trait ConfigureOtp {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError>;
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    fn erase_totp_slot(&self, slot: u8) -> Result<(), Error>;
}

/// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey


@@ 164,9 165,9 @@ pub trait GenerateOtp {
    /// ```no_run
    /// use std::time;
    /// use nitrokey::GenerateOtp;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
    /// match time {


@@ 183,7 184,7 @@ pub trait GenerateOtp {
    ///
    /// [`get_totp_code`]: #method.get_totp_code
    /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp
    fn set_time(&self, time: u64, force: bool) -> Result<(), CommandError> {
    fn set_time(&self, time: u64, force: bool) -> Result<(), Error> {
        let result = if force {
            unsafe { nitrokey_sys::NK_totp_set_time(time) }
        } else {


@@ 202,22 203,22 @@ pub trait GenerateOtp {
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::{CommandError, GenerateOtp};
    /// use nitrokey::{CommandError, Error, GenerateOtp};
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.get_hotp_slot_name(1) {
    ///     Ok(name) => println!("HOTP slot 1: {}", name),
    ///     Err(CommandError::SlotNotProgrammed) => println!("HOTP slot 1 not programmed"),
    ///     Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("HOTP slot 1 not programmed"),
    ///     Err(err) => println!("Could not get slot name: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
    fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) }
    }



@@ 231,22 232,22 @@ pub trait GenerateOtp {
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::{CommandError, GenerateOtp};
    /// use nitrokey::{CommandError, Error, GenerateOtp};
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.get_totp_slot_name(1) {
    ///     Ok(name) => println!("TOTP slot 1: {}", name),
    ///     Err(CommandError::SlotNotProgrammed) => println!("TOTP slot 1 not programmed"),
    ///     Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("TOTP slot 1 not programmed"),
    ///     Err(err) => println!("Could not get slot name: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
    fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) }
    }



@@ 263,9 264,9 @@ pub trait GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::GenerateOtp;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let code = device.get_hotp_code(1)?;
    /// println!("Generated HOTP code on slot 1: {}", code);


@@ 274,10 275,10 @@ pub trait GenerateOtp {
    /// ```
    ///
    /// [`get_config`]: trait.Device.html#method.get_config
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_hotp_code(&self, slot: u8) -> Result<String, Error> {
        unsafe {
            return result_from_string(nitrokey_sys::NK_get_hotp_code(slot));
        }


@@ 300,9 301,9 @@ pub trait GenerateOtp {
    /// ```no_run
    /// use std::time;
    /// use nitrokey::GenerateOtp;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
    /// match time {


@@ 319,10 320,10 @@ pub trait GenerateOtp {
    ///
    /// [`set_time`]: #method.set_time
    /// [`get_config`]: trait.Device.html#method.get_config
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> {
    fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
        unsafe {
            return result_from_string(nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0));
        }


@@ 395,7 396,7 @@ impl OtpSlotData {
}

impl RawOtpSlotData {
    pub fn new(data: OtpSlotData) -> Result<RawOtpSlotData, CommandError> {
    pub fn new(data: OtpSlotData) -> Result<RawOtpSlotData, Error> {
        let name = get_cstring(data.name)?;
        let secret = get_cstring(data.secret)?;
        let use_token_id = data.token_id.is_some();

M src/pws.rs => src/pws.rs +40 -40
@@ 2,9 2,8 @@ use libc;
use nitrokey_sys;

use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::util::{
    get_command_result, get_cstring, get_last_error, result_from_string, CommandError,
};
use crate::error::{CommandError, Error};
use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string};

/// The number of slots in a [`PasswordSafe`][].
///


@@ 30,9 29,9 @@ pub const SLOT_COUNT: u8 = 16;
///
/// ```no_run
/// use nitrokey::{Device, GetPasswordSafe, PasswordSafe};
/// # use nitrokey::CommandError;
/// # use nitrokey::Error;
///
/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), CommandError> {
/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), Error> {
///     let name = pws.get_slot_name(0)?;
///     let login = pws.get_slot_login(0)?;
///     let password = pws.get_slot_login(0)?;


@@ 40,7 39,7 @@ pub const SLOT_COUNT: u8 = 16;
///     Ok(())
/// }
///
/// # fn try_main() -> Result<(), CommandError> {
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::connect()?;
/// let pws = device.get_password_safe("123456")?;
/// use_password_safe(&pws);


@@ 53,6 52,7 @@ pub const SLOT_COUNT: u8 = 16;
/// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe
/// [`lock`]: trait.Device.html#method.lock
/// [`GetPasswordSafe`]: trait.GetPasswordSafe.html
#[derive(Debug)]
pub struct PasswordSafe<'a> {
    _device: &'a dyn Device,
}


@@ 89,11 89,11 @@ pub trait GetPasswordSafe {
    ///
    /// ```no_run
    /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_password_safe(pws: &PasswordSafe) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.get_password_safe("123456") {
    ///     Ok(pws) => {


@@ 110,16 110,16 @@ pub trait GetPasswordSafe {
    /// [`lock`]: trait.Device.html#method.lock
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`Device::build_aes_key`]: trait.Device.html#method.build_aes_key
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`Unknown`]: enum.CommandError.html#variant.Unknown
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError>;
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, Error>;
}

fn get_password_safe<'a>(
    device: &'a dyn Device,
    user_pin: &str,
) -> Result<PasswordSafe<'a>, CommandError> {
) -> Result<PasswordSafe<'a>, Error> {
    let user_pin_string = get_cstring(user_pin)?;
    let result = unsafe {
        get_command_result(nitrokey_sys::NK_enable_password_safe(


@@ 129,9 129,9 @@ fn get_password_safe<'a>(
    result.map(|()| PasswordSafe { _device: device })
}

fn get_pws_result(s: String) -> Result<String, CommandError> {
fn get_pws_result(s: String) -> Result<String, Error> {
    if s.is_empty() {
        Err(CommandError::SlotNotProgrammed)
        Err(CommandError::SlotNotProgrammed.into())
    } else {
        Ok(s)
    }


@@ 146,9 146,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::{GetPasswordSafe, SLOT_COUNT};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| {


@@ 161,7 161,7 @@ impl<'a> PasswordSafe<'a> {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], CommandError> {
    pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], Error> {
        let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() };
        if status_ptr.is_null() {
            return Err(get_last_error());


@@ 191,9 191,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// match device.get_password_safe("123456") {
    ///     Ok(pws) => {


@@ 208,9 208,9 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_name(&self, slot: u8) -> Result<String, CommandError> {
    pub fn get_slot_name(&self, slot: u8) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) }
            .and_then(get_pws_result)
    }


@@ 228,9 228,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;


@@ 241,9 241,9 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_login(&self, slot: u8) -> Result<String, CommandError> {
    pub fn get_slot_login(&self, slot: u8) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) }
            .and_then(get_pws_result)
    }


@@ 261,9 261,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;


@@ 274,9 274,9 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_password(&self, slot: u8) -> Result<String, CommandError> {
    pub fn get_slot_password(&self, slot: u8) -> Result<String, Error> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) }
            .and_then(get_pws_result)
    }


@@ 292,9 292,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;


@@ 305,15 305,15 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    pub fn write_slot(
        &self,
        slot: u8,
        name: &str,
        login: &str,
        password: &str,
    ) -> Result<(), CommandError> {
    ) -> Result<(), Error> {
        let name_string = get_cstring(name)?;
        let login_string = get_cstring(login)?;
        let password_string = get_cstring(password)?;


@@ 338,9 338,9 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// match pws.erase_slot(0) {


@@ 351,8 351,8 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    pub fn erase_slot(&self, slot: u8) -> Result<(), CommandError> {
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    pub fn erase_slot(&self, slot: u8) -> Result<(), Error> {
        unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) }
    }
}


@@ 365,19 365,19 @@ impl<'a> Drop for PasswordSafe<'a> {
}

impl GetPasswordSafe for Pro {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> {
        get_password_safe(self, user_pin)
    }
}

impl GetPasswordSafe for Storage {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> {
        get_password_safe(self, user_pin)
    }
}

impl GetPasswordSafe for DeviceWrapper {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> {
        get_password_safe(self, user_pin)
    }
}

M src/util.rs => src/util.rs +11 -122
@@ 1,53 1,11 @@
use std::borrow;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::{c_char, c_int};

use libc::{c_void, free};
use rand_core::RngCore;
use rand_os::OsRng;

/// Error types returned by Nitrokey device or by the library.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CommandError {
    /// A packet with a wrong checksum has been sent or received.
    WrongCrc,
    /// A command tried to access an OTP slot that does not exist.
    WrongSlot,
    /// A command tried to generate an OTP on a slot that is not configured.
    SlotNotProgrammed,
    /// The provided password is wrong.
    WrongPassword,
    /// You are not authorized for this command or provided a wrong temporary
    /// password.
    NotAuthorized,
    /// An error occurred when getting or setting the time.
    Timestamp,
    /// You did not provide a name for the OTP slot.
    NoName,
    /// This command is not supported by this device.
    NotSupported,
    /// This command is unknown.
    UnknownCommand,
    /// AES decryption failed.
    AesDecryptionFailed,
    /// An unknown error occurred.
    Unknown(i64),
    /// An unspecified error occurred.
    Undefined,
    /// You passed a string containing a null byte.
    InvalidString,
    /// A supplied string exceeded a length limit.
    StringTooLong,
    /// You passed an invalid slot.
    InvalidSlot,
    /// The supplied string was not in hexadecimal format.
    InvalidHexString,
    /// The target buffer was smaller than the source.
    TargetBufferTooSmall,
    /// An error occurred during random number generation.
    RngError,
}
use crate::error::{Error, LibraryError};

/// Log level for libnitrokey.
///


@@ 76,9 34,9 @@ pub fn owned_str_from_ptr(ptr: *const c_char) -> String {
    }
}

pub fn result_from_string(ptr: *const c_char) -> Result<String, CommandError> {
pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
    if ptr.is_null() {
        return Err(CommandError::Undefined);
        return Err(Error::UnexpectedError);
    }
    unsafe {
        let s = owned_str_from_ptr(ptr);


@@ 93,103 51,34 @@ pub fn result_from_string(ptr: *const c_char) -> Result<String, CommandError> {
    }
}

pub fn get_command_result(value: c_int) -> Result<(), CommandError> {
pub fn get_command_result(value: c_int) -> Result<(), Error> {
    match value {
        0 => Ok(()),
        other => Err(CommandError::from(other)),
        other => Err(Error::from(other)),
    }
}

pub fn get_last_result() -> Result<(), CommandError> {
pub fn get_last_result() -> Result<(), Error> {
    let value = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int;
    get_command_result(value)
}

pub fn get_last_error() -> CommandError {
pub fn get_last_error() -> Error {
    return match get_last_result() {
        Ok(()) => CommandError::Undefined,
        Ok(()) => Error::UnexpectedError,
        Err(err) => err,
    };
}

pub fn generate_password(length: usize) -> Result<Vec<u8>, CommandError> {
pub fn generate_password(length: usize) -> Result<Vec<u8>, Error> {
    let mut rng = OsRng::new()?;
    let mut data = vec![0u8; length];
    rng.fill_bytes(&mut data[..]);
    Ok(data)
}

pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, CommandError> {
    CString::new(s).or(Err(CommandError::InvalidString))
}

impl CommandError {
    fn as_str(&self) -> borrow::Cow<'static, str> {
        match *self {
            CommandError::WrongCrc => {
                "A packet with a wrong checksum has been sent or received".into()
            }
            CommandError::WrongSlot => "The given OTP slot does not exist".into(),
            CommandError::SlotNotProgrammed => "The given OTP slot is not programmed".into(),
            CommandError::WrongPassword => "The given password is wrong".into(),
            CommandError::NotAuthorized => {
                "You are not authorized for this command or provided a wrong temporary \
                 password"
                    .into()
            }
            CommandError::Timestamp => "An error occurred when getting or setting the time".into(),
            CommandError::NoName => "You did not provide a name for the OTP slot".into(),
            CommandError::NotSupported => "This command is not supported by this device".into(),
            CommandError::UnknownCommand => "This command is unknown".into(),
            CommandError::AesDecryptionFailed => "AES decryption failed".into(),
            CommandError::Unknown(x) => {
                borrow::Cow::from(format!("An unknown error occurred ({})", x))
            }
            CommandError::Undefined => "An unspecified error occurred".into(),
            CommandError::InvalidString => "You passed a string containing a null byte".into(),
            CommandError::StringTooLong => "The supplied string is too long".into(),
            CommandError::InvalidSlot => "The given slot is invalid".into(),
            CommandError::InvalidHexString => {
                "The supplied string is not in hexadecimal format".into()
            }
            CommandError::TargetBufferTooSmall => "The target buffer is too small".into(),
            CommandError::RngError => "An error occurred during random number generation".into(),
        }
    }
}

impl fmt::Display for CommandError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl From<c_int> for CommandError {
    fn from(value: c_int) -> Self {
        match value {
            1 => CommandError::WrongCrc,
            2 => CommandError::WrongSlot,
            3 => CommandError::SlotNotProgrammed,
            4 => CommandError::WrongPassword,
            5 => CommandError::NotAuthorized,
            6 => CommandError::Timestamp,
            7 => CommandError::NoName,
            8 => CommandError::NotSupported,
            9 => CommandError::UnknownCommand,
            10 => CommandError::AesDecryptionFailed,
            200 => CommandError::StringTooLong,
            201 => CommandError::InvalidSlot,
            202 => CommandError::InvalidHexString,
            203 => CommandError::TargetBufferTooSmall,
            x => CommandError::Unknown(x.into()),
        }
    }
}

impl From<rand_core::Error> for CommandError {
    fn from(_error: rand_core::Error) -> Self {
        CommandError::RngError
    }
pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, Error> {
    CString::new(s).or(Err(LibraryError::InvalidString.into()))
}

impl Into<i32> for LogLevel {

M tests/device.rs => tests/device.rs +88 -113
@@ 5,8 5,8 @@ use std::process::Command;
use std::{thread, time};

use nitrokey::{
    Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, GetPasswordSafe,
    OtpMode, OtpSlotData, Storage, VolumeMode,
    Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, Error,
    GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode,
};
use nitrokey_test::test as test_device;



@@ 31,11 31,11 @@ fn count_nitrokey_block_devices() -> usize {

#[test_device]
fn connect_no_device() {
    assert!(nitrokey::connect().is_err());
    assert!(nitrokey::connect_model(nitrokey::Model::Pro).is_err());
    assert!(nitrokey::connect_model(nitrokey::Model::Storage).is_err());
    assert!(nitrokey::Pro::connect().is_err());
    assert!(nitrokey::Storage::connect().is_err());
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect());
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect_model(nitrokey::Model::Pro));
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect_model(nitrokey::Model::Storage));
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect());
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Storage::connect());
}

#[test_device]


@@ 125,20 125,20 @@ fn get_retry_count(device: DeviceWrapper) {
fn config(device: DeviceWrapper) {
    let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap();
    let config = Config::new(None, None, None, true);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));
    let get_config = admin.get_config().unwrap();
    assert_eq!(config, get_config);

    let config = Config::new(None, Some(9), None, true);
    assert_eq!(Err(CommandError::InvalidSlot), admin.write_config(config));
    assert_lib_err!(LibraryError::InvalidSlot, admin.write_config(config));

    let config = Config::new(Some(1), None, Some(0), false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));
    let get_config = admin.get_config().unwrap();
    assert_eq!(config, get_config);

    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));
    let get_config = admin.get_config().unwrap();
    assert_eq!(config, get_config);
}


@@ 148,9 148,7 @@ fn change_user_pin(device: DeviceWrapper) {
    let device = device.authenticate_user(USER_PASSWORD).unwrap().device();
    let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0;

    assert!(device
        .change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD)
        .is_ok());
    assert_ok!((), device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD));

    let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0;
    let device = device


@@ 159,11 157,9 @@ fn change_user_pin(device: DeviceWrapper) {
        .device();

    let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD);
    assert_eq!(Err(CommandError::WrongPassword), result);
    assert_cmd_err!(CommandError::WrongPassword, result);

    assert!(device
        .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD)
        .is_ok());
    assert_ok!((), device.change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD));

    let device = device.authenticate_user(USER_PASSWORD).unwrap().device();
    assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err());


@@ 174,9 170,7 @@ fn change_admin_pin(device: DeviceWrapper) {
    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();
    let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0;

    assert!(device
        .change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD)
        .is_ok());
    assert_ok!((), device.change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD));

    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0;
    let device = device


@@ 184,14 178,12 @@ fn change_admin_pin(device: DeviceWrapper) {
        .unwrap()
        .device();

    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.change_admin_pin(ADMIN_PASSWORD, ADMIN_PASSWORD)
    );

    assert!(device
        .change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD)
        .is_ok());
    assert_ok!((), device.change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD));

    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();
    device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err();


@@ 205,18 197,19 @@ where
    let result = device.authenticate_user(password);
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(error, err.1);
    match err.1 {
        Error::CommandError(err) => assert_eq!(error, err),
        _ => assert!(false),
    };
    err.0
}

#[test_device]
fn unlock_user_pin(device: DeviceWrapper) {
    let device = device.authenticate_user(USER_PASSWORD).unwrap().device();
    assert!(device
        .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)
        .is_ok());
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD));
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD)
    );



@@ 228,13 221,11 @@ fn unlock_user_pin(device: DeviceWrapper) {
    let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword);

    // unblock with current PIN
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD)
    );
    assert!(device
        .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)
        .is_ok());
    assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD));
    let device = device.authenticate_user(USER_PASSWORD).unwrap().device();

    // block user PIN


@@ 244,57 235,47 @@ fn unlock_user_pin(device: DeviceWrapper) {
    let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword);

    // unblock with new PIN
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD)
    );
    assert!(device
        .unlock_user_pin(ADMIN_PASSWORD, USER_NEW_PASSWORD)
        .is_ok());
    assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_NEW_PASSWORD));

    // reset user PIN
    assert!(device
        .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD)
        .is_ok());
    assert_ok!((), device.change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD));
}

#[test_device]
fn factory_reset(device: DeviceWrapper) {
    let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap();
    let otp_data = OtpSlotData::new(1, "test", "0123468790", OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_totp_slot(otp_data, 30));
    assert_ok!((), admin.write_totp_slot(otp_data, 30));

    let device = admin.device();
    let pws = device.get_password_safe(USER_PASSWORD).unwrap();
    assert_eq!(Ok(()), pws.write_slot(0, "test", "testlogin", "testpw"));
    assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw"));
    drop(pws);

    assert_eq!(
        Ok(()),
        device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD)
    );
    assert_eq!(
        Ok(()),
    assert_ok!((), device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD));
    assert_ok!(
        (),
        device.change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD)
    );

    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.factory_reset(USER_NEW_PASSWORD)
    );
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.factory_reset(ADMIN_PASSWORD)
    );
    assert_eq!(Ok(()), device.factory_reset(ADMIN_NEW_PASSWORD));
    assert_ok!((), device.factory_reset(ADMIN_NEW_PASSWORD));

    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();

    let user = device.authenticate_user(USER_PASSWORD).unwrap();
    assert_eq!(
        Err(CommandError::SlotNotProgrammed),
        user.get_totp_slot_name(1)
    );
    assert_cmd_err!(CommandError::SlotNotProgrammed, user.get_totp_slot_name(1));

    let device = user.device();
    let pws = device.get_password_safe(USER_PASSWORD).unwrap();


@@ 302,20 283,20 @@ fn factory_reset(device: DeviceWrapper) {
    assert_ne!("testlogin".to_string(), pws.get_slot_login(0).unwrap());
    assert_ne!("testpw".to_string(), pws.get_slot_password(0).unwrap());

    assert_eq!(Ok(()), device.build_aes_key(ADMIN_PASSWORD));
    assert_ok!((), device.build_aes_key(ADMIN_PASSWORD));
}

#[test_device]
fn build_aes_key(device: DeviceWrapper) {
    let pws = device.get_password_safe(USER_PASSWORD).unwrap();
    assert_eq!(Ok(()), pws.write_slot(0, "test", "testlogin", "testpw"));
    assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw"));
    drop(pws);

    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.build_aes_key(USER_PASSWORD)
    );
    assert_eq!(Ok(()), device.build_aes_key(ADMIN_PASSWORD));
    assert_ok!((), device.build_aes_key(ADMIN_PASSWORD));

    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();



@@ 327,74 308,71 @@ fn build_aes_key(device: DeviceWrapper) {

#[test_device]
fn change_update_pin(device: Storage) {
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN)
    );
    assert_eq!(Ok(()), device.change_update_pin(UPDATE_PIN, UPDATE_NEW_PIN));
    assert_eq!(Ok(()), device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN));
    assert_ok!((), device.change_update_pin(UPDATE_PIN, UPDATE_NEW_PIN));
    assert_ok!((), device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN));
}

#[test_device]
fn encrypted_volume(device: Storage) {
    assert_eq!(Ok(()), device.lock());
    assert_ok!((), device.lock());

    assert_eq!(1, count_nitrokey_block_devices());
    assert_eq!(Ok(()), device.disable_encrypted_volume());
    assert_ok!((), device.disable_encrypted_volume());
    assert_eq!(1, count_nitrokey_block_devices());
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.enable_encrypted_volume("123")
    );
    assert_eq!(1, count_nitrokey_block_devices());
    assert_eq!(Ok(()), device.enable_encrypted_volume(USER_PASSWORD));
    assert_ok!((), device.enable_encrypted_volume(USER_PASSWORD));
    assert_eq!(2, count_nitrokey_block_devices());
    assert_eq!(Ok(()), device.disable_encrypted_volume());
    assert_ok!((), device.disable_encrypted_volume());
    assert_eq!(1, count_nitrokey_block_devices());
}

#[test_device]
fn hidden_volume(device: Storage) {
    assert_eq!(Ok(()), device.lock());
    assert_ok!((), device.lock());

    assert_eq!(1, count_nitrokey_block_devices());
    assert_eq!(Ok(()), device.disable_hidden_volume());
    assert_ok!((), device.disable_hidden_volume());
    assert_eq!(1, count_nitrokey_block_devices());

    assert_eq!(Ok(()), device.enable_encrypted_volume(USER_PASSWORD));
    assert_ok!((), device.enable_encrypted_volume(USER_PASSWORD));
    assert_eq!(2, count_nitrokey_block_devices());

    // TODO: why this error code?
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.create_hidden_volume(5, 0, 100, "hiddenpw")
    );
    assert_eq!(Ok(()), device.create_hidden_volume(0, 20, 21, "hidden-pw"));
    assert_eq!(
        Ok(()),
        device.create_hidden_volume(0, 20, 21, "hiddenpassword")
    );
    assert_eq!(Ok(()), device.create_hidden_volume(1, 0, 1, "otherpw"));
    assert_ok!((), device.create_hidden_volume(0, 20, 21, "hidden-pw"));
    assert_ok!((), device.create_hidden_volume(0, 20, 21, "hiddenpassword"));
    assert_ok!((), device.create_hidden_volume(1, 0, 1, "otherpw"));
    // TODO: test invalid range (not handled by libnitrokey)
    assert_eq!(2, count_nitrokey_block_devices());

    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.enable_hidden_volume("blubb")
    );
    assert_eq!(Ok(()), device.enable_hidden_volume("hiddenpassword"));
    assert_ok!((), device.enable_hidden_volume("hiddenpassword"));
    assert_eq!(2, count_nitrokey_block_devices());
    assert_eq!(Ok(()), device.enable_hidden_volume("otherpw"));
    assert_ok!((), device.enable_hidden_volume("otherpw"));
    assert_eq!(2, count_nitrokey_block_devices());

    assert_eq!(Ok(()), device.disable_hidden_volume());
    assert_ok!((), device.disable_hidden_volume());
    assert_eq!(1, count_nitrokey_block_devices());
}

#[test_device]
fn lock(device: Storage) {
    assert_eq!(Ok(()), device.enable_encrypted_volume(USER_PASSWORD));
    assert_eq!(Ok(()), device.lock());
    assert_ok!((), device.enable_encrypted_volume(USER_PASSWORD));
    assert_ok!((), device.lock());
    assert_eq!(1, count_nitrokey_block_devices());
}



@@ 410,17 388,14 @@ fn set_unencrypted_volume_mode(device: Storage) {
    }

    fn assert_success(device: &Storage, mode: VolumeMode) {
        assert_eq!(
            Ok(()),
            device.set_unencrypted_volume_mode(ADMIN_PASSWORD, mode)
        );
        assert_ok!((), device.set_unencrypted_volume_mode(ADMIN_PASSWORD, mode));
        assert_mode(&device, mode);
    }

    assert_success(&device, VolumeMode::ReadOnly);

    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.set_unencrypted_volume_mode(USER_PASSWORD, VolumeMode::ReadOnly)
    );
    assert_mode(&device, VolumeMode::ReadOnly);


@@ 460,17 435,17 @@ fn get_production_info(device: Storage) {

#[test_device]
fn clear_new_sd_card_warning(device: Storage) {
    assert_eq!(Ok(()), device.factory_reset(ADMIN_PASSWORD));
    assert_ok!((), device.factory_reset(ADMIN_PASSWORD));
    thread::sleep(time::Duration::from_secs(3));
    assert_eq!(Ok(()), device.build_aes_key(ADMIN_PASSWORD));
    assert_ok!((), device.build_aes_key(ADMIN_PASSWORD));

    // We have to perform an SD card operation to reset the new_sd_card_found field
    assert_eq!(Ok(()), device.lock());
    assert_ok!((), device.lock());

    let status = device.get_status().unwrap();
    assert!(status.new_sd_card_found);

    assert_eq!(Ok(()), device.clear_new_sd_card_warning(ADMIN_PASSWORD));
    assert_ok!((), device.clear_new_sd_card_warning(ADMIN_PASSWORD));

    let status = device.get_status().unwrap();
    assert!(!status.new_sd_card_found);


@@ 478,18 453,18 @@ fn clear_new_sd_card_warning(device: Storage) {

#[test_device]
fn export_firmware(device: Storage) {
    assert_eq!(
        Err(CommandError::WrongPassword),
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.export_firmware("someadminpn")
    );
    assert_eq!(Ok(()), device.export_firmware(ADMIN_PASSWORD));
    assert_eq!(
        Ok(()),
    assert_ok!((), device.export_firmware(ADMIN_PASSWORD));
    assert_ok!(
        (),
        device.set_unencrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadWrite)
    );
    assert_eq!(Ok(()), device.export_firmware(ADMIN_PASSWORD));
    assert_eq!(
        Ok(()),
    assert_ok!((), device.export_firmware(ADMIN_PASSWORD));
    assert_ok!(
        (),
        device.set_unencrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadOnly)
    );
}

M tests/otp.rs => tests/otp.rs +50 -67
@@ 4,8 4,8 @@ use std::fmt::Debug;
use std::ops::Deref;

use nitrokey::{
    Admin, Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, OtpMode,
    OtpSlotData,
    Admin, Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, LibraryError,
    OtpMode, OtpSlotData,
};
use nitrokey_test::test as test_device;



@@ 38,7 38,7 @@ enum TotpTimestampSize {
fn make_admin_test_device<T>(device: T) -> Admin<T>
where
    T: Device,
    (T, nitrokey::CommandError): Debug,
    (T, nitrokey::Error): Debug,
{
    device
        .authenticate_admin(ADMIN_PASSWORD)


@@ 47,7 47,7 @@ where

fn configure_hotp(admin: &ConfigureOtp, counter: u8) {
    let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_hotp_slot(slot_data, counter.into()));
    assert_ok!((), admin.write_hotp_slot(slot_data, counter.into()));
}

fn check_hotp_codes(device: &GenerateOtp, offset: u8) {


@@ 61,20 61,17 @@ fn check_hotp_codes(device: &GenerateOtp, offset: u8) {

#[test_device]
fn set_time(device: DeviceWrapper) {
    assert_eq!(Ok(()), device.set_time(1546385382, true));
    assert_eq!(Ok(()), device.set_time(1546385392, false));
    assert_eq!(
        Err(CommandError::Timestamp),
        device.set_time(1546385292, false)
    );
    assert_eq!(Ok(()), device.set_time(1546385382, true));
    assert_ok!((), device.set_time(1546385382, true));
    assert_ok!((), device.set_time(1546385392, false));
    assert_cmd_err!(CommandError::Timestamp, device.set_time(1546385292, false));
    assert_ok!((), device.set_time(1546385382, true));
}

#[test_device]
fn hotp_no_pin(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_hotp(&admin, 0);
    check_hotp_codes(admin.deref(), 0);


@@ 90,67 87,64 @@ fn hotp_no_pin(device: DeviceWrapper) {
fn hotp_pin(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, true);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_hotp(&admin, 0);
    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap();
    check_hotp_codes(&user, 0);

    assert!(user.device().get_hotp_code(1).is_err());
    assert_cmd_err!(CommandError::NotAuthorized, user.device().get_hotp_code(1));
}

#[test_device]
fn hotp_slot_name(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_hotp_slot(slot_data, 0));
    assert_ok!((), admin.write_hotp_slot(slot_data, 0));

    let device = admin.device();
    let result = device.get_hotp_slot_name(1);
    assert_eq!("test-hotp", result.unwrap());
    let result = device.get_hotp_slot_name(4);
    assert_eq!(CommandError::InvalidSlot, result.unwrap_err());
    assert_lib_err!(LibraryError::InvalidSlot, result);
}

#[test_device]
fn hotp_error(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::NoName),
        admin.write_hotp_slot(slot_data, 0)
    );
    assert_cmd_err!(CommandError::NoName, admin.write_hotp_slot(slot_data, 0));
    let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::InvalidSlot),
    assert_lib_err!(
        LibraryError::InvalidSlot,
        admin.write_hotp_slot(slot_data, 0)
    );
    let slot_data = OtpSlotData::new(1, "test", "foobar", OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::InvalidHexString),
    assert_lib_err!(
        LibraryError::InvalidHexString,
        admin.write_hotp_slot(slot_data, 0)
    );
    let code = admin.get_hotp_code(4);
    assert_eq!(CommandError::InvalidSlot, code.unwrap_err());
    assert_lib_err!(LibraryError::InvalidSlot, code);
}

#[test_device]
fn hotp_erase(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));
    let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_hotp_slot(slot_data, 0));
    assert_ok!((), admin.write_hotp_slot(slot_data, 0));
    let slot_data = OtpSlotData::new(2, "test2", HOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_hotp_slot(slot_data, 0));
    assert_ok!((), admin.write_hotp_slot(slot_data, 0));

    assert_eq!(Ok(()), admin.erase_hotp_slot(1));
    assert_ok!((), admin.erase_hotp_slot(1));

    let device = admin.device();
    let result = device.get_hotp_slot_name(1);
    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err());
    assert_cmd_err!(CommandError::SlotNotProgrammed, result);
    let result = device.get_hotp_code(1);
    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err());
    assert_cmd_err!(CommandError::SlotNotProgrammed, result);

    assert_eq!("test2", device.get_hotp_slot_name(2).unwrap());
}


@@ 158,26 152,19 @@ fn hotp_erase(device: DeviceWrapper) {
fn configure_totp(admin: &ConfigureOtp, factor: u64) {
    let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits);
    let time_window = 30u64.checked_mul(factor).unwrap();
    assert_eq!(Ok(()), admin.write_totp_slot(slot_data, time_window as u16));
    assert_ok!((), admin.write_totp_slot(slot_data, time_window as u16));
}

fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) {
    for (i, &(base_time, code)) in TOTP_CODES.iter().enumerate() {
    for (base_time, code) in TOTP_CODES {
        let time = base_time.checked_mul(factor).unwrap();
        let is_u64 = time > u32::max_value() as u64;
        if is_u64 != (timestamp_size == TotpTimestampSize::U64) {
            continue;
        }

        assert_eq!(Ok(()), device.set_time(time, true));
        let result = device.get_totp_code(1);
        assert!(result.is_ok());
        let result_code = result.unwrap();
        assert_eq!(
            code, result_code,
            "TOTP code {} should be {} but is {}",
            i, code, result_code
        );
        assert_ok!((), device.set_time(time, true));
        assert_ok!(code.to_string(), device.get_totp_code(1));
    }
}



@@ 186,7 173,7 @@ fn totp_no_pin(device: DeviceWrapper) {
    // TODO: this test may fail due to bad timing --> find solution
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_totp(&admin, 1);
    check_totp_codes(admin.deref(), 1, TotpTimestampSize::U32);


@@ 204,7 191,7 @@ fn totp_no_pin(device: DeviceWrapper) {
fn totp_no_pin_64(device: Pro) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_totp(&admin, 1);
    check_totp_codes(admin.deref(), 1, TotpTimestampSize::U64);


@@ 221,13 208,13 @@ fn totp_pin(device: DeviceWrapper) {
    // TODO: this test may fail due to bad timing --> find solution
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, true);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_totp(&admin, 1);
    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap();
    check_totp_codes(&user, 1, TotpTimestampSize::U32);

    assert!(user.device().get_totp_code(1).is_err());
    assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1));
}

#[test_device]


@@ 235,68 222,64 @@ fn totp_pin(device: DeviceWrapper) {
fn totp_pin_64(device: Pro) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, true);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));

    configure_totp(&admin, 1);
    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap();
    check_totp_codes(&user, 1, TotpTimestampSize::U64);

    assert!(user.device().get_totp_code(1).is_err());
    assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1));
}

#[test_device]
fn totp_slot_name(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits);
    assert_eq!(Ok(()), admin.write_totp_slot(slot_data, 0));
    assert_ok!((), admin.write_totp_slot(slot_data, 0));

    let device = admin.device();
    let result = device.get_totp_slot_name(1);
    assert!(result.is_ok());
    assert_eq!("test-totp", result.unwrap());
    assert_ok!("test-totp", result);
    let result = device.get_totp_slot_name(16);
    assert_eq!(CommandError::InvalidSlot, result.unwrap_err());
    assert_lib_err!(LibraryError::InvalidSlot, result);
}

#[test_device]
fn totp_error(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::NoName),
        admin.write_totp_slot(slot_data, 0)
    );
    assert_cmd_err!(CommandError::NoName, admin.write_totp_slot(slot_data, 0));
    let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::InvalidSlot),
    assert_lib_err!(
        LibraryError::InvalidSlot,
        admin.write_totp_slot(slot_data, 0)
    );
    let slot_data = OtpSlotData::new(4, "test", "foobar", OtpMode::SixDigits);
    assert_eq!(
        Err(CommandError::InvalidHexString),
    assert_lib_err!(
        LibraryError::InvalidHexString,
        admin.write_totp_slot(slot_data, 0)
    );
    let code = admin.get_totp_code(20);
    assert_eq!(CommandError::InvalidSlot, code.unwrap_err());
    assert_lib_err!(LibraryError::InvalidSlot, code);
}

#[test_device]
fn totp_erase(device: DeviceWrapper) {
    let admin = make_admin_test_device(device);
    let config = Config::new(None, None, None, false);
    assert_eq!(Ok(()), admin.write_config(config));
    assert_ok!((), admin.write_config(config));
    let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_totp_slot(slot_data, 0));
    assert_ok!((), admin.write_totp_slot(slot_data, 0));
    let slot_data = OtpSlotData::new(2, "test2", TOTP_SECRET, OtpMode::SixDigits);
    assert_eq!(Ok(()), admin.write_totp_slot(slot_data, 0));
    assert_ok!((), admin.write_totp_slot(slot_data, 0));

    assert_eq!(Ok(()), admin.erase_totp_slot(1));
    assert_ok!((), admin.erase_totp_slot(1));

    let device = admin.device();
    let result = device.get_totp_slot_name(1);
    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err());
    assert_cmd_err!(CommandError::SlotNotProgrammed, result);
    let result = device.get_totp_code(1);
    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err());
    assert_cmd_err!(CommandError::SlotNotProgrammed, result);

    assert_eq!("test2", device.get_totp_slot_name(2).unwrap());
}

M tests/pws.rs => tests/pws.rs +46 -61
@@ 3,16 3,18 @@ mod util;
use std::ffi::CStr;

use libc::{c_int, c_void, free};
use nitrokey::{CommandError, Device, GetPasswordSafe, PasswordSafe, SLOT_COUNT};
use nitrokey::{
    CommandError, Device, Error, GetPasswordSafe, LibraryError, PasswordSafe, SLOT_COUNT,
};
use nitrokey_sys;
use nitrokey_test::test as test_device;

use crate::util::{ADMIN_PASSWORD, USER_PASSWORD};

fn get_slot_name_direct(slot: u8) -> Result<String, CommandError> {
fn get_slot_name_direct(slot: u8) -> Result<String, Error> {
    let ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) };
    if ptr.is_null() {
        return Err(CommandError::Undefined);
        return Err(Error::UnexpectedError);
    }
    let s = unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() };
    unsafe { free(ptr as *mut c_void) };


@@ 21,7 23,7 @@ fn get_slot_name_direct(slot: u8) -> Result<String, CommandError> {
            let error = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int;
            match error {
                0 => Ok(s),
                other => Err(CommandError::from(other)),
                other => Err(Error::from(other)),
            }
        }
        false => Ok(s),


@@ 37,11 39,9 @@ where

#[test_device]
fn enable(device: DeviceWrapper) {
    assert!(device
        .get_password_safe(&(USER_PASSWORD.to_owned() + "123"))
        .is_err());
    assert_cmd_err!(CommandError::WrongPassword, device.get_password_safe(&(USER_PASSWORD.to_owned() + "123")));
    assert!(device.get_password_safe(USER_PASSWORD).is_ok());
    assert!(device.get_password_safe(ADMIN_PASSWORD).is_err());
    assert_cmd_err!(CommandError::WrongPassword, device.get_password_safe(ADMIN_PASSWORD));
    assert!(device.get_password_safe(USER_PASSWORD).is_ok());
}



@@ 49,35 49,35 @@ fn enable(device: DeviceWrapper) {
fn drop(device: DeviceWrapper) {
    {
        let pws = get_pws(&device);
        assert_eq!(Ok(()), pws.write_slot(1, "name", "login", "password"));
        assert_ok!((), pws.write_slot(1, "name", "login", "password"));
        assert_eq!("name", pws.get_slot_name(1).unwrap());
        let result = get_slot_name_direct(1);
        assert_eq!(Ok(String::from("name")), result);
        assert_ok!(String::from("name"), result);
    }
    let result = get_slot_name_direct(1);
    assert_eq!(Ok(String::from("name")), result);
    assert_eq!(Ok(()), device.lock());
    assert_ok!(String::from("name"), result);
    assert_ok!((), device.lock());
    let result = get_slot_name_direct(1);
    assert_eq!(Err(CommandError::NotAuthorized), result);
    assert_cmd_err!(CommandError::NotAuthorized, result);
}

#[test_device]
fn get_status(device: DeviceWrapper) {
    let pws = get_pws(&device);
    for i in 0..SLOT_COUNT {
        assert_eq!(Ok(()), pws.erase_slot(i), "Could not erase slot {}", i);
        assert_ok!((), pws.erase_slot(i));
    }
    let status = pws.get_slot_status().unwrap();
    assert_eq!(status, [false; SLOT_COUNT as usize]);

    assert_eq!(Ok(()), pws.write_slot(1, "name", "login", "password"));
    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    let status = pws.get_slot_status().unwrap();
    for i in 0..SLOT_COUNT {
        assert_eq!(i == 1, status[i as usize]);
    }

    for i in 0..SLOT_COUNT {
        assert_eq!(Ok(()), pws.write_slot(i, "name", "login", "password"));
        assert_ok!((), pws.write_slot(i, "name", "login", "password"));
    }
    let status = pws.get_slot_status().unwrap();
    assert_eq!(status, [true; SLOT_COUNT as usize]);


@@ 86,76 86,61 @@ fn get_status(device: DeviceWrapper) {
#[test_device]
fn get_data(device: DeviceWrapper) {
    let pws = get_pws(&device);
    assert_eq!(Ok(()), pws.write_slot(1, "name", "login", "password"));
    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    assert_eq!("name", pws.get_slot_name(1).unwrap());
    assert_eq!("login", pws.get_slot_login(1).unwrap());
    assert_eq!("password", pws.get_slot_password(1).unwrap());

    assert_eq!(Ok(()), pws.erase_slot(1));
    assert_eq!(Err(CommandError::SlotNotProgrammed), pws.get_slot_name(1));
    assert_eq!(Err(CommandError::SlotNotProgrammed), pws.get_slot_login(1));
    assert_eq!(
        Err(CommandError::SlotNotProgrammed),
        pws.get_slot_password(1)
    );
    assert_ok!((), pws.erase_slot(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_login(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_password(1));

    let name = "with å";
    let login = "pär@test.com";
    let password = "'i3lJc[09?I:,[u7dWz9";
    assert_eq!(Ok(()), pws.write_slot(1, name, login, password));
    assert_ok!((), pws.write_slot(1, name, login, password));
    assert_eq!(name, pws.get_slot_name(1).unwrap());
    assert_eq!(login, pws.get_slot_login(1).unwrap());
    assert_eq!(password, pws.get_slot_password(1).unwrap());

    assert_eq!(
        Err(CommandError::InvalidSlot),
        pws.get_slot_name(SLOT_COUNT)
    );
    assert_eq!(
        Err(CommandError::InvalidSlot),
        pws.get_slot_login(SLOT_COUNT)
    );
    assert_eq!(
        Err(CommandError::InvalidSlot),
        pws.get_slot_password(SLOT_COUNT)
    );
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_name(SLOT_COUNT));
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_login(SLOT_COUNT));
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_password(SLOT_COUNT));
}

#[test_device]
fn write(device: DeviceWrapper) {
    let pws = get_pws(&device);

    assert_eq!(
        Err(CommandError::InvalidSlot),
    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.write_slot(SLOT_COUNT, "name", "login", "password")
    );

    assert_eq!(Ok(()), pws.write_slot(0, "", "login", "password"));
    assert_eq!(Err(CommandError::SlotNotProgrammed), pws.get_slot_name(0));
    assert_eq!(Ok(String::from("login")), pws.get_slot_login(0));
    assert_eq!(Ok(String::from("password")), pws.get_slot_password(0));

    assert_eq!(Ok(()), pws.write_slot(0, "name", "", "password"));
    assert_eq!(Ok(String::from("name")), pws.get_slot_name(0));
    assert_eq!(Err(CommandError::SlotNotProgrammed), pws.get_slot_login(0));
    assert_eq!(Ok(String::from("password")), pws.get_slot_password(0));

    assert_eq!(Ok(()), pws.write_slot(0, "name", "login", ""));
    assert_eq!(Ok(String::from("name")), pws.get_slot_name(0));
    assert_eq!(Ok(String::from("login")), pws.get_slot_login(0));
    assert_eq!(
        Err(CommandError::SlotNotProgrammed),
        pws.get_slot_password(0)
    );
    assert_ok!((), pws.write_slot(0, "", "login", "password"));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(0));
    assert_ok!(String::from("login"), pws.get_slot_login(0));
    assert_ok!(String::from("password"), pws.get_slot_password(0));

    assert_ok!((), pws.write_slot(0, "name", "", "password"));
    assert_ok!(String::from("name"), pws.get_slot_name(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_login(0));
    assert_ok!(String::from("password"), pws.get_slot_password(0));

    assert_ok!((), pws.write_slot(0, "name", "login", ""));
    assert_ok!(String::from("name"), pws.get_slot_name(0));
    assert_ok!(String::from("login"), pws.get_slot_login(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_password(0));
}

#[test_device]
fn erase(device: DeviceWrapper) {
    let pws = get_pws(&device);
    assert_eq!(Err(CommandError::InvalidSlot), pws.erase_slot(SLOT_COUNT));
    assert_lib_err!(LibraryError::InvalidSlot, pws.erase_slot(SLOT_COUNT));

    assert_eq!(Ok(()), pws.write_slot(0, "name", "login", "password"));
    assert_eq!(Ok(()), pws.erase_slot(0));
    assert_eq!(Ok(()), pws.erase_slot(0));
    assert_eq!(Err(CommandError::SlotNotProgrammed), pws.get_slot_name(0));
    assert_ok!((), pws.write_slot(0, "name", "login", "password"));
    assert_ok!((), pws.erase_slot(0));
    assert_ok!((), pws.erase_slot(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(0));
}

M tests/util/mod.rs => tests/util/mod.rs +81 -0
@@ 1,2 1,83 @@
pub static ADMIN_PASSWORD: &str = "12345678";
pub static USER_PASSWORD: &str = "123456";

#[macro_export]
macro_rules! assert_ok {
    ($left:expr, $right:expr) => {{
        match &$right {
            Ok(right) => match &$left {
                left => {
                    if !(*left == *right) {
                        panic!(
                            r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#,
                            left, right
                        )
                    }
                }
            },
            Err(right_err) => panic!(
                r#"assertion failed: `(left == right)`
  left: `Ok({:?})`,
 right: `Err({:?})`"#,
                $left, right_err
            ),
        }
    }};
}

#[macro_export]
macro_rules! assert_err {
    ($err:path, $left:expr, $right:expr) => {
        match &$right {
            Err($err(ref right_err)) => match &$left {
                left_err => {
                    if !(*left_err == *right_err) {
                        panic!(
                            r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#,
                            left_err, right_err
                        )
                    }
                }
            },
            Err(ref right_err) => panic!(
                r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#,
                $err($left),
                right_err
            ),
            Ok(right_ok) => panic!(
                r#"assertion failed: `(left == right)`
  left: `Err({:?})`,
 right: `Ok({:?})`"#,
                $err($left),
                right_ok
            ),
        }
    };
}

#[macro_export]
macro_rules! assert_cmd_err {
    ($left:expr, $right:expr) => {
        assert_err!(::nitrokey::Error::CommandError, $left, $right);
    };
}

#[macro_export]
macro_rules! assert_cmu_err {
    ($left:expr, $right:expr) => {
        assert_err!(::nitrokey::Error::CommunicationError, $left, $right);
    };
}

#[macro_export]
macro_rules! assert_lib_err {
    ($left:expr, $right:expr) => {
        assert_err!(::nitrokey::Error::LibraryError, $left, $right);
    };
}