~ireas/nitrokey-rs

7ce751225f12c295c6e33dd46cfb5dcb88f8fbb3 — Robin Krahl 2 years ago 445e920 + 62e8ee8
Merge branch 'connection-manager-mut' into next

Refactor the connection management to prevent multiple device
connections at the same time.

RFC: https://lists.sr.ht/~ireas/nitrokey-rs-dev/%3C20190126174327.tbuyk2s535kfiqm4%40localhost%3E
M CHANGELOG.md => CHANGELOG.md +5 -0
@@ 41,6 41,11 @@ SPDX-License-Identifier: MIT
- Update the `nitrokey-sys` dependency to version 3.5.0.
- Update the `nitrokey-test` dependency to version 0.2.1 and add the
  `nitrokey-test-state` dependency in version 0.1.0.
- Refactor connection management:
  - Add `ConcurrentAccessError` and `PoisonError` `Error` variants.
  - Add the `Manager` struct that manages connections to Nitrokey devices.
  - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`.
  - Add the `into_manager` function to the `Device` trait.

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

M Cargo.toml => Cargo.toml +3 -2
@@ 17,11 17,12 @@ license = "MIT"
exclude = [".builds/*"]

[dependencies]
lazy_static = "1.2.0"
libc = "0.2"
nitrokey-sys = "3.5"
rand_core = {version = "0.3", default-features = false, features = ["std"] }
rand_os = {version = "0.1"}

[dev-dependencies]
nitrokey-test = {version = "0.2.1"}
nitrokey-test-state = {version = "0.1.0"}
nitrokey-test = {git = "https://github.com/robinkrahl/nitrokey-test", rev = "fdbe036720cf73dbb989e3a25611fa5cca4a513e"}
nitrokey-test-state = "0.1.0"

M src/auth.rs => src/auth.rs +49 -41
@@ 1,6 1,7 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::marker;
use std::ops;
use std::os::raw::c_char;
use std::os::raw::c_int;


@@ 18,7 19,7 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25;
/// Provides methods to authenticate as a user or as an admin using a PIN.  The authenticated
/// methods will consume the current device instance.  On success, they return the authenticated
/// device.  Otherwise, they return the current unauthenticated device and the error code.
pub trait Authenticate {
pub trait Authenticate<'a> {
    /// Performs user authentication.  This method consumes the device.  If successful, an
    /// authenticated device is returned.  Otherwise, the current unauthenticated device and the
    /// error are returned.


@@ 38,11 39,12 @@ pub trait Authenticate {
    /// use nitrokey::{Authenticate, DeviceWrapper, User};
    /// # use nitrokey::Error;
    ///
    /// fn perform_user_task(device: &User<DeviceWrapper>) {}
    /// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let device = match device.authenticate_user("123456") {
    ///     Ok(user) => {
    ///         perform_user_task(&user);


@@ 61,9 63,9 @@ pub trait Authenticate {
    /// [`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, Error)>
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)>
    where
        Self: Device + Sized;
        Self: Device<'a> + Sized;

    /// Performs admin authentication.  This method consumes the device.  If successful, an
    /// authenticated device is returned.  Otherwise, the current unauthenticated device and the


@@ 84,11 86,12 @@ pub trait Authenticate {
    /// use nitrokey::{Authenticate, Admin, DeviceWrapper};
    /// # use nitrokey::Error;
    ///
    /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {}
    /// fn perform_admin_task<'a>(device: &Admin<'a, DeviceWrapper<'a>>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let device = match device.authenticate_admin("123456") {
    ///     Ok(admin) => {
    ///         perform_admin_task(&admin);


@@ 107,9 110,9 @@ pub trait Authenticate {
    /// [`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, Error)>
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)>
    where
        Self: Device + Sized;
        Self: Device<'a> + Sized;
}

trait AuthenticatedDevice<T> {


@@ 128,9 131,10 @@ trait AuthenticatedDevice<T> {
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
#[derive(Debug)]
pub struct User<T: Device> {
pub struct User<'a, T: Device<'a>> {
    device: T,
    temp_password: Vec<u8>,
    marker: marker::PhantomData<&'a T>,
}

/// A Nitrokey device with admin authentication.


@@ 143,14 147,15 @@ pub struct User<T: Device> {
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
#[derive(Debug)]
pub struct Admin<T: Device> {
pub struct Admin<'a, T: Device<'a>> {
    device: T,
    temp_password: Vec<u8>,
    marker: marker::PhantomData<&'a T>,
}

fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>
fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>
where
    D: Device,
    D: Device<'a>,
    A: AuthenticatedDevice<D>,
    T: Fn(*const c_char, *const c_char) -> c_int,
{


@@ 170,14 175,14 @@ where
    }
}

fn authenticate_user_wrapper<T, C>(
fn authenticate_user_wrapper<'a, T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<User<DeviceWrapper>, (DeviceWrapper, Error)>
) -> Result<User<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
where
    T: Device,
    C: Fn(T) -> DeviceWrapper,
    T: Device<'a> + 'a,
    C: Fn(T) -> DeviceWrapper<'a>,
{
    let result = device.authenticate_user(password);
    match result {


@@ 186,14 191,14 @@ where
    }
}

fn authenticate_admin_wrapper<T, C>(
fn authenticate_admin_wrapper<'a, T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, Error)>
) -> Result<Admin<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
where
    T: Device,
    C: Fn(T) -> DeviceWrapper,
    T: Device<'a> + 'a,
    C: Fn(T) -> DeviceWrapper<'a>,
{
    let result = device.authenticate_admin(password);
    match result {


@@ 202,7 207,7 @@ where
    }
}

impl<T: Device> User<T> {
impl<'a, T: Device<'a>> User<'a, T> {
    /// Forgets the user authentication and returns an unauthenticated device.  This method
    /// consumes the authenticated device.  It does not perform any actual commands on the
    /// Nitrokey.


@@ 211,7 216,7 @@ impl<T: Device> User<T> {
    }
}

impl<T: Device> ops::Deref for User<T> {
impl<'a, T: Device<'a>> ops::Deref for User<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {


@@ 219,13 224,13 @@ impl<T: Device> ops::Deref for User<T> {
    }
}

impl<T: Device> ops::DerefMut for User<T> {
impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.device
    }
}

impl<T: Device> GenerateOtp for User<T> {
impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> {
    fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe {
            nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr())


@@ 239,11 244,12 @@ impl<T: Device> GenerateOtp for User<T> {
    }
}

impl<T: Device> AuthenticatedDevice<T> for User<T> {
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for User<'a, T> {
    fn new(device: T, temp_password: Vec<u8>) -> Self {
        User {
            device,
            temp_password,
            marker: marker::PhantomData,
        }
    }



@@ 252,7 258,7 @@ impl<T: Device> AuthenticatedDevice<T> for User<T> {
    }
}

impl<T: Device> ops::Deref for Admin<T> {
impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {


@@ 260,13 266,13 @@ impl<T: Device> ops::Deref for Admin<T> {
    }
}

impl<T: Device> ops::DerefMut for Admin<T> {
impl<'a, T: Device<'a>> ops::DerefMut for Admin<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.device
    }
}

impl<T: Device> Admin<T> {
impl<'a, T: Device<'a>> Admin<'a, T> {
    /// Forgets the user authentication and returns an unauthenticated device.  This method
    /// consumes the authenticated device.  It does not perform any actual commands on the
    /// Nitrokey.


@@ 287,7 293,8 @@ impl<T: Device> Admin<T> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let config = Config::new(None, None, None, false);
    /// match device.authenticate_admin("12345678") {
    ///     Ok(mut admin) => {


@@ 316,7 323,7 @@ impl<T: Device> Admin<T> {
    }
}

impl<T: Device> ConfigureOtp for Admin<T> {
impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> {
    fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> {
        let raw_data = RawOtpSlotData::new(data)?;
        get_command_result(unsafe {


@@ 364,11 371,12 @@ impl<T: Device> ConfigureOtp for Admin<T> {
    }
}

impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> {
    fn new(device: T, temp_password: Vec<u8>) -> Self {
        Admin {
            device,
            temp_password,
            marker: marker::PhantomData,
        }
    }



@@ 377,8 385,8 @@ impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
    }
}

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


@@ 387,7 395,7 @@ impl Authenticate for DeviceWrapper {
        }
    }

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


@@ 399,28 407,28 @@ impl Authenticate for DeviceWrapper {
    }
}

impl Authenticate for Pro {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
impl<'a> Authenticate<'a> for Pro<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, 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, Error)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })
    }
}

impl Authenticate for Storage {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
impl<'a> Authenticate<'a> for Storage<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, 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, Error)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })

M src/device.rs => src/device.rs +155 -204
@@ 2,7 2,6 @@
// SPDX-License-Identifier: MIT

use std::fmt;
use std::marker;

use libc;
use nitrokey_sys;


@@ 54,7 53,7 @@ impl fmt::Display for VolumeMode {

/// A wrapper for a Nitrokey device of unknown type.
///
/// Use the function [`connect`][] to obtain a wrapped instance.  The wrapper implements all traits
/// Use the [`connect`][] method to obtain a wrapped instance.  The wrapper implements all traits
/// that are shared between all Nitrokey devices so that the shared functionality can be used
/// without knowing the type of the underlying device.  If you want to use functionality that is
/// not available for all devices, you have to extract the device.


@@ 67,11 66,12 @@ impl fmt::Display for VolumeMode {
/// use nitrokey::{Authenticate, DeviceWrapper, User};
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<DeviceWrapper>) {}
/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
/// fn perform_other_task(device: &DeviceWrapper) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::connect()?;
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);


@@ 97,7 97,8 @@ impl fmt::Display for VolumeMode {
/// fn perform_storage_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::connect()?;
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect()?;
/// perform_common_task(&device);
/// match device {
///     DeviceWrapper::Storage(storage) => perform_storage_task(&storage),


@@ 107,21 108,20 @@ impl fmt::Display for VolumeMode {
/// # }
/// ```
///
/// [`connect`]: fn.connect.html
/// [`connect`]: struct.Manager.html#method.connect
#[derive(Debug)]
pub enum DeviceWrapper {
pub enum DeviceWrapper<'a> {
    /// A Nitrokey Storage device.
    Storage(Storage),
    Storage(Storage<'a>),
    /// A Nitrokey Pro device.
    Pro(Pro),
    Pro(Pro<'a>),
}

/// A Nitrokey Pro device without user or admin authentication.
///
/// Use the global function [`connect`][] to obtain an instance wrapper or the method
/// [`connect`][`Pro::connect`] to directly obtain an instance.  If you want to execute a command
/// that requires user or admin authentication, use [`authenticate_admin`][] or
/// [`authenticate_user`][].
/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to
/// directly obtain an instance.  If you want to execute a command that requires user or admin
/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][].
///
/// # Examples
///


@@ 131,11 131,12 @@ pub enum DeviceWrapper {
/// use nitrokey::{Authenticate, User, Pro};
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<Pro>) {}
/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {}
/// fn perform_other_task(device: &Pro) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::Pro::connect()?;
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_pro()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);


@@ 153,21 154,18 @@ pub enum DeviceWrapper {
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: fn.connect.html
/// [`Pro::connect`]: #method.connect
/// [`connect`]: struct.Manager.html#method.connect
/// [`connect_pro`]: struct.Manager.html#method.connect_pro
#[derive(Debug)]
pub struct Pro {
    // make sure that users cannot directly instantiate this type
    #[doc(hidden)]
    marker: marker::PhantomData<()>,
pub struct Pro<'a> {
    manager: Option<&'a mut crate::Manager>,
}

/// A Nitrokey Storage device without user or admin authentication.
///
/// Use the global function [`connect`][] to obtain an instance wrapper or the method
/// [`connect`][`Storage::connect`] to directly obtain an instance.  If you want to execute a
/// command that requires user or admin authentication, use [`authenticate_admin`][] or
/// [`authenticate_user`][].
/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_storage`] method to
/// directly obtain an instance.  If you want to execute a command that requires user or admin
/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][].
///
/// # Examples
///


@@ 177,11 175,12 @@ pub struct Pro {
/// use nitrokey::{Authenticate, User, Storage};
/// # use nitrokey::Error;
///
/// fn perform_user_task(device: &User<Storage>) {}
/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {}
/// fn perform_other_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let device = nitrokey::Storage::connect()?;
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_storage()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);


@@ 199,13 198,11 @@ pub struct Pro {
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: fn.connect.html
/// [`Storage::connect`]: #method.connect
/// [`connect`]: struct.Manager.html#method.connect
/// [`connect_storage`]: struct.Manager.html#method.connect_storage
#[derive(Debug)]
pub struct Storage {
    // make sure that users cannot directly instantiate this type
    #[doc(hidden)]
    marker: marker::PhantomData<()>,
pub struct Storage<'a> {
    manager: Option<&'a mut crate::Manager>,
}

/// The status of a volume on a Nitrokey Storage device.


@@ 296,7 293,32 @@ 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 + fmt::Debug {
pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
    /// Returns the [`Manager`][] instance that has been used to connect to this device.
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::{Device, DeviceWrapper};
    ///
    /// fn do_something(device: DeviceWrapper) {
    ///     // reconnect to any device
    ///     let manager = device.into_manager();
    ///     let device = manager.connect();
    ///     // do something with the device
    ///     // ...
    /// }
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// match nitrokey::take()?.connect() {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    fn into_manager(self) -> &'a mut crate::Manager;

    /// Returns the model of the connected Nitrokey device.
    ///
    /// # Example


@@ 306,7 328,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// println!("Connected to a Nitrokey {}", device.get_model());
    /// #    Ok(())
    /// # }


@@ 322,7 345,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.get_serial_number() {
    ///     Ok(number) => println!("serial no: {}", number),
    ///     Err(err) => eprintln!("Could not get serial number: {}", err),


@@ 344,7 368,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let count = device.get_user_retry_count();
    /// match device.get_user_retry_count() {
    ///     Ok(count) => println!("{} remaining authentication attempts (user)", count),
    ///     Err(err) => eprintln!("Could not get user retry count: {}", err),


@@ 366,7 392,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let count = device.get_admin_retry_count();
    /// match device.get_admin_retry_count() {
    ///     Ok(count) => println!("{} remaining authentication attempts (admin)", count),


@@ 388,7 415,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.get_firmware_version() {
    ///     Ok(version) => println!("Firmware version: {}", version),
    ///     Err(err) => eprintln!("Could not access firmware version: {}", err),


@@ 411,7 439,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let config = device.get_config()?;
    /// println!("numlock binding:          {:?}", config.numlock);
    /// println!("capslock binding:         {:?}", config.capslock);


@@ 445,7 474,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.change_admin_pin("12345678", "12345679") {
    ///     Ok(()) => println!("Updated admin PIN."),
    ///     Err(err) => eprintln!("Failed to update admin PIN: {}", err),


@@ 478,7 508,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.change_user_pin("123456", "123457") {
    ///     Ok(()) => println!("Updated admin PIN."),
    ///     Err(err) => eprintln!("Failed to update admin PIN: {}", err),


@@ 511,7 542,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.unlock_user_pin("12345678", "123456") {
    ///     Ok(()) => println!("Unlocked user PIN."),
    ///     Err(err) => eprintln!("Failed to unlock user PIN: {}", err),


@@ 545,7 577,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.lock() {
    ///     Ok(()) => println!("Locked the Nitrokey device."),
    ///     Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err),


@@ 576,7 609,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.factory_reset("12345678") {
    ///     Ok(()) => println!("Performed a factory reset."),
    ///     Err(err) => eprintln!("Could not perform a factory reset: {}", err),


@@ 610,7 644,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.build_aes_key("12345678") {
    ///     Ok(()) => println!("New AES keys have been built."),
    ///     Err(err) => eprintln!("Could not build new AES keys: {}", err),


@@ 626,67 661,6 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
    }
}

/// Connects to a Nitrokey device.  This method can be used to connect to any connected device,
/// both a Nitrokey Pro and a Nitrokey Storage.
///
/// # Errors
///
/// - [`NotConnected`][] if no Nitrokey device is connected
///
/// # Example
///
/// ```
/// use nitrokey::DeviceWrapper;
///
/// fn do_something(device: DeviceWrapper) {}
///
/// match nitrokey::connect() {
///     Ok(device) => do_something(device),
///     Err(err) => eprintln!("Could not connect to a Nitrokey: {}", err),
/// }
/// ```
///
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
pub fn connect() -> Result<DeviceWrapper, Error> {
    if unsafe { nitrokey_sys::NK_login_auto() } == 1 {
        match get_connected_device() {
            Some(wrapper) => Ok(wrapper),
            None => Err(CommunicationError::NotConnected.into()),
        }
    } else {
        Err(CommunicationError::NotConnected.into())
    }
}

/// Connects to a Nitrokey device of the given model.
///
/// # Errors
///
/// - [`NotConnected`][] if no Nitrokey device of the given model is connected
///
/// # Example
///
/// ```
/// use nitrokey::DeviceWrapper;
/// use nitrokey::Model;
///
/// fn do_something(device: DeviceWrapper) {}
///
/// match nitrokey::connect_model(Model::Pro) {
///     Ok(device) => do_something(device),
///     Err(err) => eprintln!("Could not connect to a Nitrokey Pro: {}", err),
/// }
/// ```
///
/// [`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(CommunicationError::NotConnected.into())
    }
}

fn get_connected_model() -> Option<Model> {
    match unsafe { nitrokey_sys::NK_get_device_model() } {
        nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro),


@@ 695,18 669,26 @@ fn get_connected_model() -> Option<Model> {
    }
}

fn create_device_wrapper(model: Model) -> DeviceWrapper {
pub(crate) fn create_device_wrapper(
    manager: &mut crate::Manager,
    model: Model,
) -> DeviceWrapper<'_> {
    match model {
        Model::Pro => Pro::new().into(),
        Model::Storage => Storage::new().into(),
        Model::Pro => Pro::new(manager).into(),
        Model::Storage => Storage::new(manager).into(),
    }
}

fn get_connected_device() -> Option<DeviceWrapper> {
    get_connected_model().map(create_device_wrapper)
pub(crate) fn get_connected_device(
    manager: &mut crate::Manager,
) -> Result<DeviceWrapper<'_>, Error> {
    match get_connected_model() {
        Some(model) => Ok(create_device_wrapper(manager, model)),
        None => Err(CommunicationError::NotConnected.into()),
    }
}

fn connect_enum(model: Model) -> bool {
pub(crate) fn connect_enum(model: Model) -> bool {
    let model = match model {
        Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
        Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,


@@ 714,15 696,15 @@ fn connect_enum(model: Model) -> bool {
    unsafe { nitrokey_sys::NK_login_enum(model) == 1 }
}

impl DeviceWrapper {
    fn device(&self) -> &dyn Device {
impl<'a> DeviceWrapper<'a> {
    fn device(&self) -> &dyn Device<'a> {
        match *self {
            DeviceWrapper::Storage(ref storage) => storage,
            DeviceWrapper::Pro(ref pro) => pro,
        }
    }

    fn device_mut(&mut self) -> &mut dyn Device {
    fn device_mut(&mut self) -> &mut dyn Device<'a> {
        match *self {
            DeviceWrapper::Storage(ref mut storage) => storage,
            DeviceWrapper::Pro(ref mut pro) => pro,


@@ 730,19 712,19 @@ impl DeviceWrapper {
    }
}

impl From<Pro> for DeviceWrapper {
    fn from(device: Pro) -> Self {
impl<'a> From<Pro<'a>> for DeviceWrapper<'a> {
    fn from(device: Pro<'a>) -> Self {
        DeviceWrapper::Pro(device)
    }
}

impl From<Storage> for DeviceWrapper {
    fn from(device: Storage) -> Self {
impl<'a> From<Storage<'a>> for DeviceWrapper<'a> {
    fn from(device: Storage<'a>) -> Self {
        DeviceWrapper::Storage(device)
    }
}

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


@@ 760,7 742,14 @@ impl GenerateOtp for DeviceWrapper {
    }
}

impl Device for DeviceWrapper {
impl<'a> Device<'a> for DeviceWrapper<'a> {
    fn into_manager(self) -> &'a mut crate::Manager {
        match self {
            DeviceWrapper::Pro(dev) => dev.into_manager(),
            DeviceWrapper::Storage(dev) => dev.into_manager(),
        }
    }

    fn get_model(&self) -> Model {
        match *self {
            DeviceWrapper::Pro(_) => Model::Pro,


@@ 769,44 758,15 @@ impl Device for DeviceWrapper {
    }
}

impl Pro {
    /// Connects to a Nitrokey Pro.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::Pro;
    ///
    /// fn use_pro(device: Pro) {}
    ///
    /// match nitrokey::Pro::connect() {
    ///     Ok(device) => use_pro(device),
    ///     Err(err) => eprintln!("Could not connect to the Nitrokey Pro: {}", err),
    /// }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect() -> Result<Pro, Error> {
        // TODO: maybe Option instead of Result?
        if connect_enum(Model::Pro) {
            Ok(Pro::new())
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }

    fn new() -> Pro {
impl<'a> Pro<'a> {
    pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> {
        Pro {
            marker: marker::PhantomData,
            manager: Some(manager),
        }
    }
}

impl Drop for Pro {
impl<'a> Drop for Pro<'a> {
    fn drop(&mut self) {
        unsafe {
            nitrokey_sys::NK_logout();


@@ 814,47 774,22 @@ impl Drop for Pro {
    }
}

impl Device for Pro {
impl<'a> Device<'a> for Pro<'a> {
    fn into_manager(mut self) -> &'a mut crate::Manager {
        self.manager.take().unwrap()
    }

    fn get_model(&self) -> Model {
        Model::Pro
    }
}

impl GenerateOtp for Pro {}

impl Storage {
    /// Connects to a Nitrokey Storage.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::Storage;
    ///
    /// fn use_storage(device: Storage) {}
    ///
    /// match nitrokey::Storage::connect() {
    ///     Ok(device) => use_storage(device),
    ///     Err(err) => eprintln!("Could not connect to the Nitrokey Storage: {}", err),
    /// }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect() -> Result<Storage, Error> {
        // TODO: maybe Option instead of Result?
        if connect_enum(Model::Storage) {
            Ok(Storage::new())
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }
impl<'a> GenerateOtp for Pro<'a> {}

    fn new() -> Storage {
impl<'a> Storage<'a> {
    pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {
        Storage {
            marker: marker::PhantomData,
            manager: Some(manager),
        }
    }



@@ 875,7 810,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.change_update_pin("12345678", "87654321") {
    ///     Ok(()) => println!("Updated update PIN."),
    ///     Err(err) => eprintln!("Failed to update update PIN: {}", err),


@@ 912,7 848,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.enable_firmware_update("12345678") {
    ///     Ok(()) => println!("Nitrokey entered update mode."),
    ///     Err(err) => eprintln!("Could not enter update mode: {}", err),


@@ 946,7 883,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => println!("Enabled the encrypted volume."),
    ///     Err(err) => eprintln!("Could not enable the encrypted volume: {}", err),


@@ 975,7 913,8 @@ impl Storage {
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => {
    ///         println!("Enabled the encrypted volume.");


@@ 1021,7 960,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {
    ///     Ok(()) => println!("Enabled a hidden volume."),


@@ 1054,7 994,8 @@ impl Storage {
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {
    ///     Ok(()) => {


@@ 1101,7 1042,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// device.enable_encrypted_volume("123445")?;
    /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?;
    /// #     Ok(())


@@ 1141,7 1083,8 @@ impl Storage {
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) {
    ///     Ok(()) => println!("Set the unencrypted volume to read-write mode."),
    ///     Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err),


@@ 1186,7 1129,8 @@ impl Storage {
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) {
    ///     Ok(()) => println!("Set the encrypted volume to read-write mode."),
    ///     Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err),


@@ 1224,7 1168,8 @@ impl Storage {
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect_storage()?;
    /// match device.get_status() {
    ///     Ok(status) => {
    ///         println!("SD card ID: {:#x}", status.serial_number_sd_card);


@@ 1267,7 1212,8 @@ impl Storage {
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect_storage()?;
    /// match device.get_production_info() {
    ///     Ok(data) => {
    ///         println!("SD card ID:   {:#x}", data.sd_card.serial_number);


@@ 1315,7 1261,8 @@ impl Storage {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::Storage::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.clear_new_sd_card_warning("12345678") {
    ///     Ok(()) => println!("Cleared the new SD card warning."),
    ///     Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err),


@@ 1360,7 1307,7 @@ impl Storage {
    }
}

impl Drop for Storage {
impl<'a> Drop for Storage<'a> {
    fn drop(&mut self) {
        unsafe {
            nitrokey_sys::NK_logout();


@@ 1368,13 1315,17 @@ impl Drop for Storage {
    }
}

impl Device for Storage {
impl<'a> Device<'a> for Storage<'a> {
    fn into_manager(mut self) -> &'a mut crate::Manager {
        self.manager.take().unwrap()
    }

    fn get_model(&self) -> Model {
        Model::Storage
    }
}

impl GenerateOtp for Storage {}
impl<'a> GenerateOtp for Storage<'a> {}

impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {
    fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {

M src/error.rs => src/error.rs +27 -3
@@ 5,6 5,7 @@ use std::error;
use std::fmt;
use std::os::raw;
use std::str;
use std::sync;

use crate::device;



@@ 13,11 14,15 @@ use crate::device;
pub enum Error {
    /// An error reported by the Nitrokey device in the response packet.
    CommandError(CommandError),
    /// A device communication.
    /// A device communication error.
    CommunicationError(CommunicationError),
    /// An error occurred due to concurrent access to the Nitrokey device.
    ConcurrentAccessError,
    /// A library usage error.
    LibraryError(LibraryError),
    /// An error that occured during random number generation.
    /// An error that occurred due to a poisoned lock.
    PoisonError(sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>),
    /// An error that occurred during random number generation.
    RandError(Box<dyn error::Error>),
    /// An error that is caused by an unexpected value returned by libnitrokey.
    UnexpectedError,


@@ 65,7 70,22 @@ impl From<str::Utf8Error> for Error {
    }
}

impl<T: device::Device> From<(T, Error)> for Error {
impl From<sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>> for Error {
    fn from(error: sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>) -> Self {
        Error::PoisonError(error)
    }
}

impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Error {
    fn from(error: sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>) -> Self {
        match error {
            sync::TryLockError::Poisoned(err) => err.into(),
            sync::TryLockError::WouldBlock => Error::ConcurrentAccessError,
        }
    }
}

impl<'a, T: device::Device<'a>> From<(T, Error)> for Error {
    fn from((_, err): (T, Error)) -> Self {
        err
    }


@@ 76,7 96,9 @@ impl error::Error for Error {
        match *self {
            Error::CommandError(ref err) => Some(err),
            Error::CommunicationError(ref err) => Some(err),
            Error::ConcurrentAccessError => None,
            Error::LibraryError(ref err) => Some(err),
            Error::PoisonError(ref err) => Some(err),
            Error::RandError(ref err) => Some(err.as_ref()),
            Error::UnexpectedError => None,
            Error::UnknownError(_) => None,


@@ 90,7 112,9 @@ impl fmt::Display for Error {
        match *self {
            Error::CommandError(ref err) => write!(f, "Command error: {}", err),
            Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err),
            Error::ConcurrentAccessError => write!(f, "Internal error: concurrent access"),
            Error::LibraryError(ref err) => write!(f, "Library error: {}", err),
            Error::PoisonError(_) => write!(f, "Internal error: poisoned lock"),
            Error::RandError(ref err) => write!(f, "RNG error: {}", err),
            Error::UnexpectedError => write!(f, "An unexpected error occurred"),
            Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err),

M src/lib.rs => src/lib.rs +259 -13
@@ 9,13 9,16 @@
//! performed without authentication, some require user access, and some require admin access.
//! This is modelled using the types [`User`][] and [`Admin`][].
//!
//! Use [`connect`][] to connect to any Nitrokey device.  The method will return a
//! You can only connect to one Nitrokey at a time.  Use the global [`take`][] function to obtain
//! an reference to the [`Manager`][] singleton that keeps track of the connections.  Then use the
//! [`connect`][] method to connect to any Nitrokey device.  The method will return a
//! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices.  You can also use
//! [`Pro::connect`][] or [`Storage::connect`][] to connect to a specific device.
//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific
//! device.
//!
//! You can then use [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated
//! device that can perform operations that require authentication.  You can use [`device`][] to go
//! back to the unauthenticated device.
//! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device
//! that can perform operations that require authentication.  You can use [`device`][] to go back
//! to the unauthenticated device.
//!
//! This makes sure that you can only execute a command if you have the required access rights.
//! Otherwise, your code will not compile.  The only exception are the methods to generate one-time


@@ 31,7 34,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
//! let device = nitrokey::connect()?;
//! let mut manager = nitrokey::take()?;
//! let device = manager.connect()?;
//! println!("{}", device.get_serial_number()?);
//! #     Ok(())
//! # }


@@ 44,7 48,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
//! let device = nitrokey::connect()?;
//! let mut manager = nitrokey::take()?;
//! let device = manager.connect()?;
//! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
//! match device.authenticate_admin("12345678") {
//!     Ok(mut admin) => {


@@ 66,7 71,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
//! let mut device = nitrokey::connect()?;
//! let mut manager = nitrokey::take()?;
//! let mut device = manager.connect()?;
//! match device.get_hotp_code(1) {
//!     Ok(code) => println!("Generated HOTP code: {}", code),
//!     Err(err) => eprintln!("Could not generate HOTP code: {}", err),


@@ 77,9 83,12 @@
//!
//! [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
//! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
//! [`connect`]: fn.connect.html
//! [`Pro::connect`]: struct.Pro.html#fn.connect.html
//! [`Storage::connect`]: struct.Storage.html#fn.connect.html
//! [`take`]: fn.take.html
//! [`connect`]: struct.Manager.html#method.connect
//! [`connect_model`]: struct.Manager.html#method.connect_model
//! [`connect_pro`]: struct.Manager.html#method.connect_pro
//! [`connect_storage`]: struct.Manager.html#method.connect_storage
//! [`manager`]: trait.Device.html#method.manager
//! [`device`]: struct.User.html#method.device
//! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code
//! [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code


@@ 89,6 98,9 @@

#![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)]

#[macro_use(lazy_static)]
extern crate lazy_static;

mod auth;
mod config;
mod device;


@@ 98,14 110,17 @@ mod pws;
mod util;

use std::fmt;
use std::marker;
use std::sync;

use nitrokey_sys;

pub use crate::auth::{Admin, Authenticate, User};
pub use crate::config::Config;
#[allow(deprecated)]
pub use crate::device::{
    connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage,
    StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
    Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus,
    VolumeMode, VolumeStatus,
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};


@@ 117,6 132,10 @@ pub const DEFAULT_ADMIN_PIN: &str = "12345678";
/// The default user PIN for all Nitrokey devices.
pub const DEFAULT_USER_PIN: &str = "123456";

lazy_static! {
    static ref MANAGER: sync::Mutex<Manager> = sync::Mutex::new(Manager::new());
}

/// A version of the libnitrokey library.
///
/// Use the [`get_library_version`](fn.get_library_version.html) function to query the library


@@ 147,6 166,233 @@ impl fmt::Display for Version {
    }
}

/// A manager for connections to Nitrokey devices.
///
/// Currently, libnitrokey only provides access to one Nitrokey device at the same time.  This
/// manager struct makes sure that `nitrokey-rs` does not try to connect to two devices at the same
/// time.
///
/// To obtain a reference to an instance of this manager, use the [`take`][] function.  Use one of
/// the connect methods – [`connect`][], [`connect_model`][], [`connect_pro`][] or
/// [`connect_storage`][] – to retrieve a [`Device`][] instance.
///
/// # Examples
///
/// Connect to a single device:
///
/// ```no_run
/// use nitrokey::Device;
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect()?;
/// println!("{}", device.get_serial_number()?);
/// #     Ok(())
/// # }
/// ```
///
/// Connect to a Pro and a Storage device:
///
/// ```no_run
/// use nitrokey::{Device, Model};
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_model(Model::Pro)?;
/// println!("Pro: {}", device.get_serial_number()?);
/// drop(device);
/// let device = manager.connect_model(Model::Storage)?;
/// println!("Storage: {}", device.get_serial_number()?);
/// #     Ok(())
/// # }
/// ```
///
/// [`connect`]: #method.connect
/// [`connect_model`]: #method.connect_model
/// [`connect_pro`]: #method.connect_pro
/// [`connect_storage`]: #method.connect_storage
/// [`manager`]: trait.Device.html#method.manager
/// [`take`]: fn.take.html
/// [`Device`]: trait.Device.html
#[derive(Debug)]
pub struct Manager {
    marker: marker::PhantomData<()>,
}

impl Manager {
    fn new() -> Self {
        Manager {
            marker: marker::PhantomData,
        }
    }

    /// Connects to a Nitrokey device.
    ///
    /// This method can be used to connect to any connected device, both a Nitrokey Pro and a
    /// Nitrokey Storage.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::DeviceWrapper;
    ///
    /// fn do_something(device: DeviceWrapper) {}
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// let mut manager = nitrokey::take()?;
    /// match manager.connect() {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect(&mut self) -> Result<DeviceWrapper<'_>, Error> {
        if unsafe { nitrokey_sys::NK_login_auto() } == 1 {
            device::get_connected_device(self)
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }

    /// Connects to a Nitrokey device of the given model.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::DeviceWrapper;
    /// use nitrokey::Model;
    ///
    /// fn do_something(device: DeviceWrapper) {}
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// match nitrokey::take()?.connect_model(Model::Pro) {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect_model(&mut self, model: Model) -> Result<DeviceWrapper<'_>, Error> {
        if device::connect_enum(model) {
            Ok(device::create_device_wrapper(self, model))
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }

    /// Connects to a Nitrokey Pro.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::Pro;
    ///
    /// fn use_pro(device: Pro) {}
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// match nitrokey::take()?.connect_pro() {
    ///     Ok(device) => use_pro(device),
    ///     Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect_pro(&mut self) -> Result<Pro<'_>, Error> {
        if device::connect_enum(device::Model::Pro) {
            Ok(device::Pro::new(self))
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }

    /// Connects to a Nitrokey Storage.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::Storage;
    ///
    /// fn use_storage(device: Storage) {}
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// match nitrokey::take()?.connect_storage() {
    ///     Ok(device) => use_storage(device),
    ///     Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect_storage(&mut self) -> Result<Storage<'_>, Error> {
        if device::connect_enum(Model::Storage) {
            Ok(Storage::new(self))
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }
}

/// Take an instance of the connection manager, blocking until an instance is available.
///
/// There may only be one [`Manager`][] instance at the same time.  If there already is an
/// instance, this method blocks.  If you want a non-blocking version, use [`take`][].
///
/// # Errors
///
/// - [`PoisonError`][] if the lock is poisoned
///
/// [`take`]: fn.take.html
/// [`PoisonError`]: struct.Error.html#variant.PoisonError
/// [`Manager`]: struct.Manager.html
pub fn take_blocking() -> Result<sync::MutexGuard<'static, Manager>, Error> {
    MANAGER.lock().map_err(Into::into)
}

/// Try to take an instance of the connection manager.
///
/// There may only be one [`Manager`][] instance at the same time.  If there already is an
/// instance, a [`ConcurrentAccessError`][] is returned.  If you want a blocking version, use
/// [`take_blocking`][].
///
/// # Errors
///
/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked
/// - [`PoisonError`][] if the lock is poisoned
///
/// [`take_blocking`]: fn.take_blocking.html
/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError
/// [`PoisonError`]: struct.Error.html#variant.PoisonError
/// [`Manager`]: struct.Manager.html
pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
    MANAGER.try_lock().map_err(Into::into)
}

/// Enables or disables debug output.  Calling this method with `true` is equivalent to setting the
/// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see
/// [`set_log_level`][]).

M src/otp.rs => src/otp.rs +18 -9
@@ 35,7 35,8 @@ pub trait ConfigureOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
    /// match device.authenticate_admin("12345678") {
    ///     Ok(mut admin) => {


@@ 71,7 72,8 @@ pub trait ConfigureOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits);
    /// match device.authenticate_admin("12345678") {
    ///     Ok(mut admin) => {


@@ 104,7 106,8 @@ pub trait ConfigureOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(mut admin) => {
    ///         match admin.erase_hotp_slot(1) {


@@ 134,7 137,8 @@ pub trait ConfigureOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(mut admin) => {
    ///         match admin.erase_totp_slot(1) {


@@ 171,7 175,8 @@ pub trait GenerateOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
    /// match time {
    ///     Ok(time) => device.set_time(time.as_secs(), false)?,


@@ 209,7 214,8 @@ pub trait GenerateOtp {
    /// use nitrokey::{CommandError, Error, GenerateOtp};
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.get_hotp_slot_name(1) {
    ///     Ok(name) => println!("HOTP slot 1: {}", name),
    ///     Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("HOTP slot 1 not programmed"),


@@ 238,7 244,8 @@ pub trait GenerateOtp {
    /// use nitrokey::{CommandError, Error, GenerateOtp};
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.get_totp_slot_name(1) {
    ///     Ok(name) => println!("TOTP slot 1: {}", name),
    ///     Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("TOTP slot 1 not programmed"),


@@ 270,7 277,8 @@ pub trait GenerateOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let code = device.get_hotp_code(1)?;
    /// println!("Generated HOTP code on slot 1: {}", code);
    /// #     Ok(())


@@ 305,7 313,8 @@ pub trait GenerateOtp {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
    /// match time {
    ///     Ok(time) => {

M src/pws.rs => src/pws.rs +31 -23
@@ 43,7 43,8 @@ pub const SLOT_COUNT: u8 = 16;
/// }
///
/// # fn try_main() -> Result<(), Error> {
/// let mut device = nitrokey::connect()?;
/// let mut manager = nitrokey::take()?;
/// let mut device = manager.connect()?;
/// let pws = device.get_password_safe("123456")?;
/// use_password_safe(&pws);
/// drop(pws);


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

/// Provides access to a [`PasswordSafe`][].


@@ 67,7 68,7 @@ pub struct PasswordSafe<'a> {
/// retrieved from it.
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
pub trait GetPasswordSafe {
pub trait GetPasswordSafe<'a> {
    /// Enables and returns the password safe.
    ///
    /// The underlying device must always live at least as long as a password safe retrieved from


@@ 98,7 99,8 @@ pub trait GetPasswordSafe {
    /// fn use_password_safe(pws: &PasswordSafe) {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.get_password_safe("123456") {
    ///     Ok(pws) => {
    ///         use_password_safe(&pws);


@@ 117,13 119,13 @@ pub trait GetPasswordSafe {
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`Unknown`]: enum.CommandError.html#variant.Unknown
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error>;
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error>;
}

fn get_password_safe<'a>(
    device: &'a dyn Device,
fn get_password_safe<'a, 'b>(
    device: &'a dyn Device<'b>,
    user_pin: &str,
) -> Result<PasswordSafe<'a>, Error> {
) -> Result<PasswordSafe<'a, 'b>, Error> {
    let user_pin_string = get_cstring(user_pin)?;
    get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) })
        .map(|_| PasswordSafe { _device: device })


@@ 137,7 139,7 @@ fn get_pws_result(s: String) -> Result<String, Error> {
    }
}

impl<'a> PasswordSafe<'a> {
impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// Returns the status of all password slots.
    ///
    /// The status indicates whether a slot is programmed or not.


@@ 149,7 151,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| {
    ///     let status = match *programmed {


@@ 194,7 197,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.get_password_safe("123456") {
    ///     Ok(pws) => {
    ///         let name = pws.get_slot_name(0)?;


@@ 231,7 235,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;
    /// let login = pws.get_slot_login(0)?;


@@ 264,7 269,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;
    /// let login = pws.get_slot_login(0)?;


@@ 295,7 301,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let name = pws.get_slot_name(0)?;
    /// let login = pws.get_slot_login(0)?;


@@ 341,7 348,8 @@ impl<'a> PasswordSafe<'a> {
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut device = nitrokey::connect()?;
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let mut pws = device.get_password_safe("123456")?;
    /// match pws.erase_slot(0) {
    ///     Ok(()) => println!("Erased slot 0."),


@@ 357,27 365,27 @@ impl<'a> PasswordSafe<'a> {
    }
}

impl<'a> Drop for PasswordSafe<'a> {
impl<'a, 'b> Drop for PasswordSafe<'a, 'b> {
    fn drop(&mut self) {
        // TODO: disable the password safe -- NK_lock_device has side effects on the Nitrokey
        // Storage, see https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65
    }
}

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

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

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

M tests/device.rs => tests/device.rs +26 -19
@@ 33,20 33,19 @@ fn count_nitrokey_block_devices() -> usize {

#[test_device]
fn connect_no_device() {
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect());
    assert_cmu_err!(
        CommunicationError::NotConnected,
        nitrokey::connect_model(nitrokey::Model::Pro)
    );
    let mut manager = nitrokey::take().unwrap();

    assert_cmu_err!(CommunicationError::NotConnected, manager.connect());
    assert_cmu_err!(
        CommunicationError::NotConnected,
        nitrokey::connect_model(nitrokey::Model::Storage)
        manager.connect_model(nitrokey::Model::Pro)
    );
    assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect());
    assert_cmu_err!(
        CommunicationError::NotConnected,
        nitrokey::Storage::connect()
        manager.connect_model(nitrokey::Model::Storage)
    );
    assert_cmu_err!(CommunicationError::NotConnected, manager.connect_pro());
    assert_cmu_err!(CommunicationError::NotConnected, manager.connect_storage());
}

#[test_device]


@@ 54,9 53,10 @@ fn connect_pro(device: Pro) {
    assert_eq!(device.get_model(), nitrokey::Model::Pro);
    drop(device);

    assert_any_ok!(nitrokey::connect());
    assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Pro));
    assert_any_ok!(nitrokey::Pro::connect());
    let mut manager = nitrokey::take().unwrap();
    assert_any_ok!(manager.connect());
    assert_any_ok!(manager.connect_model(nitrokey::Model::Pro));
    assert_any_ok!(manager.connect_pro());
}

#[test_device]


@@ 64,9 64,10 @@ fn connect_storage(device: Storage) {
    assert_eq!(device.get_model(), nitrokey::Model::Storage);
    drop(device);

    assert_any_ok!(nitrokey::connect());
    assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Storage));
    assert_any_ok!(nitrokey::Storage::connect());
    let mut manager = nitrokey::take().unwrap();
    assert_any_ok!(manager.connect());
    assert_any_ok!(manager.connect_model(nitrokey::Model::Storage));
    assert_any_ok!(manager.connect_storage());
}

fn assert_empty_serial_number() {


@@ 97,7 98,10 @@ fn get_firmware_version(device: Pro) {
    assert!(version.minor > 0);
}

fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T {
fn admin_retry<'a, T>(device: T, suffix: &str, count: u8) -> T
where
    T: Authenticate<'a> + Device<'a> + 'a,
{
    let result = device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix));
    let device = match result {
        Ok(admin) => admin.device(),


@@ 107,7 111,10 @@ fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> 
    return device;
}

fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T {
fn user_retry<'a, T>(device: T, suffix: &str, count: u8) -> T
where
    T: Authenticate<'a> + Device<'a> + 'a,
{
    let result = device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix));
    let device = match result {
        Ok(admin) => admin.device(),


@@ 216,10 223,10 @@ fn change_admin_pin(device: DeviceWrapper) {
    device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err();
}

fn require_failed_user_login<D>(device: D, password: &str, error: CommandError) -> D
fn require_failed_user_login<'a, D>(device: D, password: &str, error: CommandError) -> D
where
    D: Device + Authenticate,
    nitrokey::User<D>: std::fmt::Debug,
    D: Device<'a> + Authenticate<'a> + 'a,
    nitrokey::User<'a, D>: std::fmt::Debug,
{
    let result = device.authenticate_user(password);
    assert!(result.is_err());

M tests/lib.rs => tests/lib.rs +16 -0
@@ 10,3 10,19 @@ fn get_library_version() {
    assert!(version.git.is_empty() || version.git.starts_with("v"));
    assert!(version.major > 0);
}

#[test]
fn take_manager() {
    assert!(nitrokey::take().is_ok());

    let result = nitrokey::take();
    assert!(result.is_ok());
    let result2 = nitrokey::take();
    match result2 {
        Ok(_) => panic!("Expected error, got Ok(_)!"),
        Err(nitrokey::Error::ConcurrentAccessError) => {}
        Err(err) => panic!("Expected ConcurrentAccessError, got {}", err),
    }
    drop(result);
    assert!(nitrokey::take().is_ok());
}

M tests/otp.rs => tests/otp.rs +2 -2
@@ 36,9 36,9 @@ enum TotpTimestampSize {
    U64,
}

fn make_admin_test_device<T>(device: T) -> Admin<T>
fn make_admin_test_device<'a, T>(device: T) -> Admin<'a, T>
where
    T: Device,
    T: Device<'a>,
    (T, nitrokey::Error): Debug,
{
    unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN))

M tests/pws.rs => tests/pws.rs +2 -2
@@ 32,9 32,9 @@ fn get_slot_name_direct(slot: u8) -> Result<String, Error> {
    }
}

fn get_pws<T>(device: &mut T) -> PasswordSafe
fn get_pws<'a, T>(device: &mut T) -> PasswordSafe<'_, 'a>
where
    T: Device,
    T: Device<'a>,
{
    unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN))
}