~ireas/nitrokey-rs

6f382f93d01fd0db9eddb9349b43c64f43e5e353 — Robin Krahl 4 months ago 283d210 + 15c84b7 master
Merge branch 'release-v0.9.0'
9 files changed, 328 insertions(+), 71 deletions(-)

M .builds/archlinux.yml
M .builds/lint.yml
M CHANGELOG.md
M Cargo.toml
M src/lib.rs
M src/pws.rs
M src/util.rs
M tests/device.rs
M tests/pws.rs
M .builds/archlinux.yml => .builds/archlinux.yml +1 -0
@@ 4,6 4,7 @@ image: archlinux
packages:
  - rust
  - hidapi
  - libusb
  - gcc
sources:
  - https://git.sr.ht/~ireas/nitrokey-rs

M .builds/lint.yml => .builds/lint.yml +1 -0
@@ 3,6 3,7 @@
image: archlinux
packages:
  - gnupg
  - python-pip
  - reuse
sources:
  - https://git.sr.ht/~ireas/nitrokey-rs

M CHANGELOG.md => CHANGELOG.md +12 -1
@@ 1,8 1,19 @@
<!---
Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
Copyright (C) 2019-2021 Robin Krahl <robin.krahl@ireas.org>
SPDX-License-Identifier: CC0-1.0
-->

# v0.9.0 (2021-03-28)
- Refactor the PWS slot access to clearly distinguish unprogrammed slots and
  empty name, login or password values:
  - Add the `get_slots`, `get_slot` and `get_slot_unchecked` methods to the
    `PasswordSafe` struct and the `PasswordSlot` struct for accessing the PWS
    data.
  - Deprecate the `get_status`, `get_slot_name`, `get_slot_login` and
    `get_slot_password` method of the `PasswordSafe` struct.
- Add the `PasswordSafe::get_slot_count` method to access the number of slots
  in the password safe and deprecate the `SLOT_COUNT` constant.

# v0.8.0 (2020-09-29)
- Export the `FirmwareVersion` struct.
- Mark the `Error`, `CommandError`, `CommunicationError`, `LibraryError`,

M Cargo.toml => Cargo.toml +2 -2
@@ 1,9 1,9 @@
# Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
# Copyright (C) 2019-2021 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0

[package]
name = "nitrokey"
version = "0.8.0"
version = "0.9.0"
authors = ["Robin Krahl <robin.krahl@ireas.org>"]
edition = "2018"
homepage = "https://code.ireas.org/nitrokey-rs/"

M src/lib.rs => src/lib.rs +10 -1
@@ 143,11 143,20 @@ pub use crate::device::{
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, PasswordSlot};
pub use crate::util::LogLevel;

use crate::util::{get_cstring, get_last_result};

/// The number of slots in a [`PasswordSafe`][].
///
/// This constant is deprecated.  Use [`PasswordSafe::get_slot_count`][] instead.
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
/// [`PasswordSafe::get_slot_count`]: struct.PasswordSafe.html#method.get_slot_count
#[deprecated(since = "0.9.0", note = "Use PasswordSafe::get_slot_count instead")]
pub const SLOT_COUNT: u8 = 16;

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

M src/pws.rs => src/pws.rs +217 -23
@@ 2,21 2,18 @@
// SPDX-License-Identifier: MIT

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

/// The number of slots in a [`PasswordSafe`][].
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
pub const SLOT_COUNT: u8 = 16;
const SLOT_COUNT: u8 = 16;

/// A password safe on a Nitrokey device.
///
/// The password safe stores a tuple consisting of a name, a login and a password on a slot.  The
/// number of available slots is [`SLOT_COUNT`][].  The slots are addressed starting with zero.  To
/// retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][] method from
/// the [`GetPasswordSafe`][] trait.  Note that the device must live at least as long as the
/// password safe.
/// The password safe stores a tuple consisting of a name, a login and a password on a slot.  Use
/// [`get_slot_count`][] to access the number of available slots.  The slots are addressed starting
/// with zero.  To retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][]
/// method from the [`GetPasswordSafe`][] trait.  Note that the device must live at least as long
/// as the password safe.
///
/// Once the password safe has been unlocked, it can be accessed without a password.  Therefore it
/// is mandatory to call [`lock`][] on the corresponding device after the password store is used.


@@ 32,10 29,10 @@ pub const SLOT_COUNT: u8 = 16;
/// # use nitrokey::Error;
///
/// 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)?;
///     println!("Credentials for {}: login {}, password {}", name, login, password);
///     let slot = pws.get_slot(0)?;
///     println!("Credentials for {}:", slot.get_name()?);
///     println!("login:    {}", slot.get_login()?);
///     println!("password: {}", slot.get_password()?);
///     Ok(())
/// }
///


@@ 50,7 47,7 @@ pub const SLOT_COUNT: u8 = 16;
/// # }
/// ```
///
/// [`SLOT_COUNT`]: constant.SLOT_COUNT.html
/// [`get_slot_count`]: #method.get_slot_count
/// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe
/// [`lock`]: trait.Device.html#method.lock
/// [`GetPasswordSafe`]: trait.GetPasswordSafe.html


@@ 59,6 56,15 @@ pub struct PasswordSafe<'a, 'b> {
    _device: &'a dyn Device<'b>,
}

/// A slot of a [`PasswordSafe`][].
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
#[derive(Clone, Copy, Debug)]
pub struct PasswordSlot<'p, 'a, 'b> {
    slot: u8,
    _pws: &'p PasswordSafe<'a, 'b>,
}

/// Provides access to a [`PasswordSafe`][].
///
/// The device that implements this trait must always live at least as long as a password safe


@@ 137,14 143,21 @@ fn get_pws_result(s: String) -> Result<String, Error> {
}

impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// Returns the number of slots in this password safe.
    pub fn get_slot_count(&self) -> u8 {
        SLOT_COUNT
    }

    /// Returns the status of all password slots.
    ///
    /// The status indicates whether a slot is programmed or not.
    ///
    /// This method is deprecated.  Use [`get_slots`][] instead.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::{GetPasswordSafe, SLOT_COUNT};
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {


@@ 161,7 174,10 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], Error> {
    ///
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slots() instead")]
    pub fn get_slot_status(&self) -> Result<[bool; 16], Error> {
        let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() };
        if status_ptr.is_null() {
            return Err(get_last_error());


@@ 178,10 194,143 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
        Ok(result)
    }

    /// Returns all password slots.
    ///
    /// The returned vector contains one element per password slot.  If the slot is not programmed,
    /// the element is `None`.  Otherwise it is a [`PasswordSlot`][] instance for the slot.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// for (idx, slot) in pws.get_slots()?.iter().enumerate() {
    ///     let status = match *slot {
    ///         Some(_) => "programmed",
    ///         None => "not programmed",
    ///     };
    ///     println!("Slot {}: {}", idx, status);
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    pub fn get_slots(&self) -> Result<Vec<Option<PasswordSlot<'_, 'a, 'b>>>, Error> {
        let mut slots = Vec::new();
        let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() };
        if status_ptr.is_null() {
            return Err(get_last_error());
        }
        let status_array_ptr = status_ptr as *const [u8; SLOT_COUNT as usize];
        let status_array = unsafe { *status_array_ptr };
        for slot in 0..SLOT_COUNT {
            if status_array[usize::from(slot)] == 1 {
                slots.push(Some(PasswordSlot { slot, _pws: self }));
            } else {
                slots.push(None);
            }
        }
        unsafe {
            nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
        }
        Ok(slots)
    }

    /// Returns the slot with the given index if the slot is programmed.
    ///
    /// This method uses [`get_slots`][] to check whether the slot is programmed.  To access the
    /// slot without checking whether it is programmed, use [`get_slot_unchecked`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range
    /// - [`SlotNotProgrammed`][] if the slot is not programmed
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let slot = pws.get_slot(0)?;
    /// let name = slot.get_name()?;
    /// let login = slot.get_login()?;
    /// let password = slot.get_password()?;
    /// println!("Credentials for {}: login {}, password {}", name, login, password);
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    pub fn get_slot(&self, slot: u8) -> Result<PasswordSlot<'_, 'a, 'b>, Error> {
        let slot = usize::from(slot);
        let slots = self.get_slots()?;
        if slot < slots.len() {
            slots[slot].ok_or_else(|| CommandError::SlotNotProgrammed.into())
        } else {
            Err(LibraryError::InvalidSlot.into())
        }
    }

    /// Returns the slot with the given index without checking whether it is programmed.
    ///
    /// To check whether a slot is programmed, use [`get_slots`][] or [`get_slot`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let slot = pws.get_slot_unchecked(0)?;
    /// let name = slot.get_name()?;
    /// let login = slot.get_login()?;
    /// let password = slot.get_password()?;
    /// println!("Credentials for {}: login {}, password {}", name, login, password);
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`get_slot`]: #method.get_slot
    /// [`get_slots`]: #method.get_slots
    pub fn get_slot_unchecked(&self, slot: u8) -> Result<PasswordSlot<'_, 'a, 'b>, Error> {
        if slot < self.get_slot_count() {
            Ok(PasswordSlot { slot, _pws: self })
        } else {
            Err(LibraryError::InvalidSlot.into())
        }
    }

    /// Returns the name of the given slot (if it is programmed).
    ///
    /// This method also returns a `SlotNotProgrammed` error if the name is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_name`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 200,7 349,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///     Ok(pws) => {
    ///         let name = pws.get_slot_name(0)?;
    ///         let login = pws.get_slot_login(0)?;
    ///         let password = pws.get_slot_login(0)?;
    ///         let password = pws.get_slot_password(0)?;
    ///         println!("Credentials for {}: login {}, password {}", name, login, password);
    ///     },
    ///     Err(err) => eprintln!("Could not open the password safe: {}", err),


@@ 210,7 359,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_name`]: struct.PasswordSlot.html#method.get_name
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_name() instead")]
    pub fn get_slot_name(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) })
            .and_then(get_pws_result)


@@ 220,6 375,10 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///
    /// This method also returns a `SlotNotProgrammed` error if the login is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_login`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 244,7 403,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_login`]: struct.PasswordSlot.html#method.get_login
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_login() instead")]
    pub fn get_slot_login(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(slot) })
            .and_then(get_pws_result)


@@ 254,6 419,10 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///
    /// This method also returns a `SlotNotProgrammed` error if the password is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_password`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 278,7 447,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_password`]: struct.PasswordSlot.html#method.get_password
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_password() instead")]
    pub fn get_slot_password(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(slot) })
            .and_then(get_pws_result)


@@ 300,11 475,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// # fn try_main() -> Result<(), Error> {
    /// 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)?;
    /// let password = pws.get_slot_login(0)?;
    /// println!("Credentials for {}: login {}, password {}", name, login, password);
    /// let mut pws = device.get_password_safe("123456")?;
    /// pws.write_slot(0, "rust-lang.org", "admin", "passw0rd")?;
    /// #     Ok(())
    /// # }
    /// ```


@@ 369,6 541,28 @@ impl<'a, 'b> Drop for PasswordSafe<'a, 'b> {
    }
}

impl<'p, 'a, 'b> PasswordSlot<'p, 'a, 'b> {
    /// Returns the index of this PWS slot.
    pub fn index(&self) -> u8 {
        self.slot
    }

    /// Returns the name stored in this PWS slot.
    pub fn get_name(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(self.slot) })
    }

    /// Returns the login stored in this PWS slot.
    pub fn get_login(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(self.slot) })
    }

    /// Returns the password stored in this PWS slot.
    pub fn get_password(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(self.slot) })
    }
}

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

M src/util.rs => src/util.rs +3 -3
@@ 113,9 113,9 @@ pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, Error> {
    CString::new(s).map_err(|_| LibraryError::InvalidString.into())
}

impl Into<i32> for LogLevel {
    fn into(self) -> i32 {
        match self {
impl From<LogLevel> for i32 {
    fn from(log_level: LogLevel) -> i32 {
        match log_level {
            LogLevel::Error => 0,
            LogLevel::Warning => 1,
            LogLevel::Info => 2,

M tests/device.rs => tests/device.rs +10 -6
@@ 408,9 408,11 @@ fn factory_reset(device: DeviceWrapper) {

    let mut device = user.device();
    let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN));
    assert_utf8_err_or_ne("test", pws.get_slot_name(0));
    assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0));
    assert_utf8_err_or_ne("testpw", pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_utf8_err_or_ne("test", slot.get_name());
    assert_utf8_err_or_ne("testlogin", slot.get_login());
    assert_utf8_err_or_ne("testpw", slot.get_password());
    drop(pws);

    assert_ok!(3, device.get_user_retry_count());


@@ 436,9 438,11 @@ fn build_aes_key(device: DeviceWrapper) {
        .device();

    let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN));
    assert_utf8_err_or_ne("test", pws.get_slot_name(0));
    assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0));
    assert_utf8_err_or_ne("testpw", pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_utf8_err_or_ne("test", slot.get_name());
    assert_utf8_err_or_ne("testlogin", slot.get_login());
    assert_utf8_err_or_ne("testpw", slot.get_password());
}

#[test_device]

M tests/pws.rs => tests/pws.rs +72 -35
@@ 7,8 7,8 @@ use std::ffi::CStr;

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


@@ 60,7 60,9 @@ fn drop(device: DeviceWrapper) {
    {
        let mut pws = get_pws(&mut device);
        assert_ok!((), pws.write_slot(1, "name", "login", "password"));
        assert_ok!("name".to_string(), pws.get_slot_name(1));

        let slot = unwrap_ok!(pws.get_slot_unchecked(1));
        assert_ok!("name".to_string(), slot.get_name());
        let result = get_slot_name_direct(1);
        assert_ok!(String::from("name"), result);
    }


@@ 73,24 75,37 @@ fn drop(device: DeviceWrapper) {

#[test_device]
fn get_status(device: DeviceWrapper) {
    fn get_pws_status(pws: &PasswordSafe<'_, '_>) -> Vec<Option<u8>> {
        unwrap_ok!(pws.get_slots())
            .iter()
            .map(|opt| opt.as_ref().map(PasswordSlot::index))
            .collect()
    }

    let mut device = device;
    let mut pws = get_pws(&mut device);
    for i in 0..SLOT_COUNT {
    let slot_count = pws.get_slot_count();
    for i in 0..slot_count {
        assert_ok!((), pws.erase_slot(i));
    }
    let status = unwrap_ok!(pws.get_slot_status());
    assert_eq!(status, [false; SLOT_COUNT as usize]);
    assert_eq!(vec![None; slot_count.into()], get_pws_status(&pws));

    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    let status = unwrap_ok!(pws.get_slot_status());
    for i in 0..SLOT_COUNT {
        assert_eq!(i == 1, status[i as usize]);
    for (i, slot) in get_pws_status(&pws).into_iter().enumerate() {
        if i == 1 {
            assert_eq!(Some(1), slot);
        } else {
            assert_eq!(None, slot);
        }
    }

    for i in 0..SLOT_COUNT {
    for i in 0..slot_count {
        assert_ok!((), pws.write_slot(i, "name", "login", "password"));
    }
    assert_ok!([true; SLOT_COUNT as usize], pws.get_slot_status());
    assert_eq!(
        (0..slot_count).map(Some).collect::<Vec<_>>(),
        get_pws_status(&pws)
    );
}

#[test_device]


@@ 98,26 113,39 @@ fn get_data(device: DeviceWrapper) {
    let mut device = device;
    let mut pws = get_pws(&mut device);
    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    assert_ok!("name".to_string(), pws.get_slot_name(1));
    assert_ok!("login".to_string(), pws.get_slot_login(1));
    assert_ok!("password".to_string(), pws.get_slot_password(1));

    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!("name".to_string(), slot.get_name());
    assert_ok!("login".to_string(), slot.get_login());
    assert_ok!("password".to_string(), slot.get_password());
    drop(slot);

    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));

    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(1));
    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
    drop(slot);

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

    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));
    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(name.to_string(), slot.get_name());
    assert_ok!(login.to_string(), slot.get_login());
    assert_ok!(password.to_string(), slot.get_password());
    drop(slot);

    let slot_count = pws.get_slot_count();
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot(slot_count));
    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.get_slot_unchecked(slot_count)
    );
}

#[test_device]


@@ 127,33 155,42 @@ fn write(device: DeviceWrapper) {

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

    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));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    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));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    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));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
}

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

    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));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(0));
}