~ireas/nitrokey-rs

528e56a0ff759ea81b61eea368cf53b5540dc89a — Robin Krahl 1 year, 9 months ago 0911616 + e810570
Merge branch 'release-0.4.0'
28 files changed, 3253 insertions(+), 2272 deletions(-)

A .builds/archlinux-msrv.yml
R .builds/{archlinux-use-system-lib.yaml => archlinux-use-system-lib.yml}
M .builds/archlinux.yml
A .builds/lint.yml
M .gitignore
M CHANGELOG.md
M Cargo.toml
A LICENSES/CC0-1.0.txt
R LICENSE => LICENSES/MIT.txt
M README.md
M TODO.md
M src/auth.rs
M src/config.rs
D src/device.rs
A src/device/mod.rs
A src/device/pro.rs
A src/device/storage.rs
A src/device/wrapper.rs
A src/error.rs
M src/lib.rs
M src/otp.rs
M src/pws.rs
M src/util.rs
M tests/device.rs
M tests/lib.rs
M tests/otp.rs
M tests/pws.rs
M tests/util/mod.rs
A .builds/archlinux-msrv.yml => .builds/archlinux-msrv.yml +22 -0
@@ 0,0 1,22 @@
# Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0
image: archlinux
packages:
  - rustup
  - libnitrokey
environment:
  USE_SYSTEM_LIBNITROKEY: "1"
sources:
  - https://git.ireas.org/nitrokey-rs
tasks:
  - setup: |
      rustup set profile minimal
      rustup default 1.34.2
  - version: |
      rustc -V
  - build: |
      cd nitrokey-rs
      cargo build --release
  - test: |
      cd nitrokey-rs
      cargo test

R .builds/archlinux-use-system-lib.yaml => .builds/archlinux-use-system-lib.yml +7 -0
@@ 1,3 1,5 @@
# Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0
image: archlinux
packages:
  - rust


@@ 7,6 9,8 @@ environment:
sources:
  - https://git.ireas.org/nitrokey-rs
tasks:
  - version: |
      rustc -V
  - build: |
      cd nitrokey-rs
      cargo build --release


@@ 16,3 20,6 @@ tasks:
  - format: |
      cd nitrokey-rs
      cargo fmt -- --check
  - clippy: |
      cd nitrokey-rs
      cargo clippy -- -D warnings

M .builds/archlinux.yml => .builds/archlinux.yml +2 -3
@@ 1,3 1,5 @@
# Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0
image: archlinux
packages:
  - rust


@@ 12,6 14,3 @@ tasks:
  - test: |
      cd nitrokey-rs
      cargo test
  - format: |
      cd nitrokey-rs
      cargo fmt -- --check

A .builds/lint.yml => .builds/lint.yml +16 -0
@@ 0,0 1,16 @@
# Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0
image: archlinux
packages:
  - gnupg
  - reuse
sources:
  - https://git.ireas.org/nitrokey-rs
tasks:
  - verify: |
      cd nitrokey-rs
      curl -s "https://pgp.ireas.org/0x6D533958F070C57C.txt" | gpg --import
      git verify-commit HEAD
  - reuse: |
      cd nitrokey-rs
      reuse lint

M .gitignore => .gitignore +2 -1
@@ 1,4 1,5 @@

# Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
# SPDX-License-Identifier: CC0-1.0
/target
/nitrokey-sys/target
**/*.rs.bk

M CHANGELOG.md => CHANGELOG.md +55 -0
@@ 1,3 1,58 @@
<!---
Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
SPDX-License-Identifier: CC0-1.0
-->

# v0.4.0 (2020-01-02)
- Remove the `test-pro` and `test-storage` features.
- Implement `Display` for `Version`.
- Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants.
- Refactor the error handling code:
  - Implement `std::error::Error` for `CommandError`.
  - Add the `Error` enum.
  - Add the `LibraryError` enum and move the library error variants from
    `CommandError` to `LibraryError`.
  - Add the `CommunicationError` enum and move the communication error variants
    from `CommandError` to `CommunicationError`.
  - Return `Error` instead of `CommandError` in all public functions.
  - Move the `CommandError::RngError` variant to `Error::RandError` and the
    `CommandError::Unknown` variant to `Error::UnknownError`.
  - Return `CommunicationError::NotConnected` instead of
    `CommandError::Undefined` from the connect functions.
  - Remove the `CommandError::Undefined` variant.
- Add a private `PhantomData` field to `Pro` and `Storage` to make direct
  instantiation impossible.
- Refactor and clean up internal code:
  - Prefer using the `Into` trait over numeric casting.
  - Add `Pro::new` and `Storage::new` functions.
- Implement `From<Pro>` and `From<Storage>` for `DeviceWrapper`.
- Add `Error::Utf8Error` variant.
  - Return `Result<Version>` instead of `Version` from `get_library_version`.
  - Return `Error::Utf8Error` if libnitrokey returns an invalid UTF-8 string.
- Implement `From<(T: Device, Error)>` for `Error`.
- Fix timing issues with the `totp_no_pin` and `totp_pin` test cases.
- Always return a `Result` in functions that communicate with a device.
- Combine `get_{major,minor}_firmware_version` into `get_firmware_version`.
- Add `set_encrypted_volume_mode` to `Storage`.
- Use mutability to represent changes to the device status:
  - Implement `DerefMut` for `User<T>` and `Admin<T>`.
  - Add `device_mut` method to `DeviceWrapper`.
  - Require a mutable `Device` reference if a method changes the device state.
- Update dependencies:
  - `nitrokey-sys` to 3.5
  - `nitrokey-test` to 0.3
  - `rand_core` to 0.5
  - `rand_os` to 0.2
- Add `nitrokey-test-state` dependency in version 0.1.
- 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.
  - Add the `force_take` function that ignores a `PoisonError` when accessing
    the manager instance.
- Internally refactor the `device` module into submodules.

# v0.3.5 (2019-12-16)
- Update the nitrokey-sys dependency version specification to ~3.4.


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

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


@@ 11,16 14,14 @@ keywords = ["nitrokey", "otp"]
categories = ["api-bindings"]
readme = "README.md"
license = "MIT"

[features]
test-pro = []
test-storage = []
exclude = [".builds/*"]

[dependencies]
lazy_static = "1.2"
libc = "0.2"
nitrokey-sys = "~3.4"
rand_core = {version = "0.3", default-features = false}
rand_os = {version = "0.1"}
nitrokey-sys = "3.5"
rand_core = {version = "0.5.1", features = ["getrandom"] }

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

A LICENSES/CC0-1.0.txt => LICENSES/CC0-1.0.txt +121 -0
@@ 0,0 1,121 @@
Creative Commons Legal Code

CC0 1.0 Universal

    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

  i. the right to reproduce, adapt, distribute, perform, display,
     communicate, and translate a Work;
 ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
     likeness depicted in a Work;
 iv. rights protecting against unfair competition in regards to a Work,
     subject to the limitations in paragraph 4(a), below;
  v. rights protecting the extraction, dissemination, use and reuse of data
     in a Work;
 vi. database rights (such as those arising under Directive 96/9/EC of the
     European Parliament and of the Council of 11 March 1996 on the legal
     protection of databases, and under any national implementation
     thereof, including any amended or successor version of such
     directive); and
vii. other similar, equivalent or corresponding rights throughout the
     world based on applicable law or treaty, and any national
     implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

 a. No trademark or patent rights held by Affirmer are waived, abandoned,
    surrendered, licensed or otherwise affected by this document.
 b. Affirmer offers the Work as-is and makes no representations or
    warranties of any kind concerning the Work, express, implied,
    statutory or otherwise, including without limitation warranties of
    title, merchantability, fitness for a particular purpose, non
    infringement, or the absence of latent or other defects, accuracy, or
    the present or absence of errors, whether or not discoverable, all to
    the greatest extent permissible under applicable law.
 c. Affirmer disclaims responsibility for clearing rights of other persons
    that may apply to the Work or any use thereof, including without
    limitation any person's Copyright and Related Rights in the Work.
    Further, Affirmer disclaims responsibility for obtaining any necessary
    consents, permissions or other rights required for any use of the
    Work.
 d. Affirmer understands and acknowledges that Creative Commons is not a
    party to this document and has no duty or obligation with respect to
    this CC0 or use of the Work.

R LICENSE => LICENSES/MIT.txt +3 -0
@@ 1,3 1,6 @@
Valid-License-Identifier: MIT
License-Text:

The MIT License (MIT)

Copyright (c) 2018 Robin Krahl <robin.krahl@ireas.org>

M README.md => README.md +28 -17
@@ 1,3 1,8 @@
<!---
Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
SPDX-License-Identifier: CC0-1.0
-->

# nitrokey-rs

A libnitrokey wrapper for Rust providing access to Nitrokey devices.


@@ 23,19 28,18 @@ supported by `nitrokey-rs`:

- `NK_get_device_model`.  We know which model we connected to, so we can
  provide this information without calling `libnitrokey`.
- `NK_get_time`.  This method is useless as it will always cause a timestamp
  error on the device (see [pull request #114][] for `libnitrokey` for details).
- `NK_get_status`.  This method only provides a string representation of
  data that can be accessed by other methods (firmware version, serial number,
  configuration).
- `NK_get_status_storage_as_string`.  This method only provides an incomplete
  string representation of the data returned by `NK_get_status_storage`.
- `NK_is_AES_supported`.  This method is no longer needed for Nitrokey devices
  with a recent firmware version.
- `NK_set_unencrypted_volume_rorw_pin_type_user`,
  `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`,
  `NK_set_encrypted_read_only` and `NK_set_encrypted_read_write`.  These
  `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`.  These
  methods are only relevant for older firmware versions (pre-v0.51).  As the
  Nitrokey Storage firmware can be updated easily, we do not support these
  outdated versions.
- `NK_totp_get_time`, `NK_status`.  These functions are deprecated.
- `NK_read_HOTP_slot`.  This function is only available for HOTP slots, not for
  TOTP.  We will support it once both types are supported by `libnitrokey`.
- All `*_as_string` functions that return string representations of data
  returned by other functions.

## Tests



@@ 51,16 55,18 @@ Note that the tests assume that the device’s passwords are the factory default
(admin PIN `12345678`, user PIN `123456`, update password `12345678`) and that
an AES key has been built.  Some tests will overwrite the data stored on the
Nitrokey device or perform a factory reset.  Never execute the tests if you
unless yout want to destroy all data on all connected Nitrokey devices!

The `totp_no_pin` and `totp_pin` tests can occasionally fail due to bad timing.
don’t want to destroy all data on any connected Nitrokey device!

## Acknowledgments

Thanks to Nitrokey UG for providing a Nitrokey Storage to support the
Thanks to Nitrokey UG for providing two Nitrokey devices to support the
development of this crate.  Thanks to Daniel Mueller for contributions to
`nitrokey-rs` and for the `nitrokey-test` crate.

## Minimum Supported Rust Version

This crate supports Rust 1.34.2 or later.

## Contact

For bug reports, patches, feature requests or other messages, please send a


@@ 68,14 74,19 @@ mail to [nitrokey-rs-dev@ireas.org][].

## License

This project is licensed under the [MIT License][].  `libnitrokey` is licensed
under the [LGPL-3.0][].
This project is licensed under the [MIT][] license.  The documentation and
configuration files contained in this repository are licensed under the
[Creative Commons Zero][CC0] license.  You can find a copy of the license texts
in the `LICENSES` directory.  `libnitrokey` is licensed under the [LGPL-3.0][].

`nitrokey-rs` complies with [version 3.0 of the REUSE specification][reuse].

[Documentation]: https://docs.rs/nitrokey
[Nitrokey udev rules]: https://www.nitrokey.com/documentation/frequently-asked-questions-faq#openpgp-card-not-available
[`libnitrokey`]: https://github.com/nitrokey/libnitrokey
[`nitrokey-test`]: https://github.com/d-e-s-o/nitrokey-test
[nitrokey-rs-dev@ireas.org]: mailto:nitrokey-rs-dev@ireas.org
[pull request #114]: https://github.com/Nitrokey/libnitrokey/pull/114
[MIT license]: https://opensource.org/licenses/MIT
[MIT]: https://opensource.org/licenses/MIT
[CC0]: https://creativecommons.org/publicdomain/zero/1.0/
[LGPL-3.0]: https://opensource.org/licenses/lgpl-3.0.html
[reuse]: https://reuse.software/practices/3.0/

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

- Add support for the currently unsupported commands:
    - `NK_is_AES_supported`
    - `NK_send_startup`
    - `NK_fill_SD_card_with_random_data`
    - `NK_get_SD_usage_data_as_string`
    - `NK_get_SD_usage_data`
    - `NK_get_progress_bar_value`
    - `NK_list_devices_by_cpuID`
    - `NK_connect_with_ID`
- Fix timing issues with the `totp_no_pin` and `totp_pin` test cases.
    - `NK_get_status`
    - `NK_list_devices`
    - `NK_free_device_info`
    - `NK_connect_with_path`
    - `NK_enable_firmware_update_pro`
    - `NK_change_firmware_password_pro`
- Clear passwords from memory.
- Find a nicer syntax for the `write_config` test.
- Prevent construction of internal types.
- More specific error checking in the tests.
- Check integer conversions.
- Consider implementing `Into<CommandError>` for `(Device, CommandError)`
- Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware
  issue 65][]).
- Disable creation of multiple password safes at the same time.

M src/auth.rs => src/auth.rs +122 -102
@@ 1,4 1,9 @@
use std::ops::Deref;
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::convert::TryFrom as _;
use std::marker;
use std::ops;
use std::os::raw::c_char;
use std::os::raw::c_int;



@@ 6,17 11,16 @@ use nitrokey_sys;

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

static TEMPORARY_PASSWORD_LENGTH: usize = 25;

/// 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.


@@ 34,20 38,21 @@ pub trait Authenticate {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, DeviceWrapper, User};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn perform_user_task(device: &User<DeviceWrapper>) {}
    /// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let device = match device.authenticate_user("123456") {
    ///     Ok(user) => {
    ///         perform_user_task(&user);
    ///         user.device()
    ///     },
    ///     Err((device, err)) => {
    ///         println!("Could not authenticate as user: {}", err);
    ///         eprintln!("Could not authenticate as user: {}", err);
    ///         device
    ///     },
    /// };


@@ 56,12 61,12 @@ pub trait Authenticate {
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`RngError`]: enum.CommandError.html#variant.RngError
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)>
    fn authenticate_user(self, password: &str) -> Result<User<'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


@@ 80,20 85,21 @@ pub trait Authenticate {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, Admin, DeviceWrapper};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {}
    /// fn perform_admin_task<'a>(device: &Admin<'a, DeviceWrapper<'a>>) {}
    /// fn perform_other_task(device: &DeviceWrapper) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let device = match device.authenticate_admin("123456") {
    ///     Ok(admin) => {
    ///         perform_admin_task(&admin);
    ///         admin.device()
    ///     },
    ///     Err((device, err)) => {
    ///         println!("Could not authenticate as admin: {}", err);
    ///         eprintln!("Could not authenticate as admin: {}", err);
    ///         device
    ///     },
    /// };


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

trait AuthenticatedDevice<T> {
    fn new(device: T, temp_password: Vec<u8>) -> Self;

    fn temp_password_ptr(&self) -> *const c_char;
}

/// A Nitrokey device with user authentication.


@@ 124,9 132,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.


@@ 139,14 148,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, CommandError)>
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,
{


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

fn authenticate_user_wrapper<T, C>(
fn authenticate_user_wrapper<'a, T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<User<DeviceWrapper>, (DeviceWrapper, CommandError)>
) -> 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 {


@@ 182,14 192,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, CommandError)>
) -> 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 {


@@ 198,7 208,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.


@@ 207,7 217,7 @@ impl<T: Device> User<T> {
    }
}

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

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


@@ 215,38 225,41 @@ impl<T: Device> Deref for User<T> {
    }
}

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

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

    fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe {
            nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, self.temp_password_ptr())
        })
    }
}

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,
        }
    }

    fn temp_password_ptr(&self) -> *const c_char {
        self.temp_password.as_ptr() as *const c_char
    }
}

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

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


@@ 254,7 267,13 @@ impl<T: Device> Deref for Admin<T> {
    }
}

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

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.


@@ 272,50 291,43 @@ impl<T: Device> Admin<T> {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, Config};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let config = Config::new(None, None, None, false);
    /// match device.authenticate_admin("12345678") {
    ///     Ok(admin) => {
    ///     Ok(mut admin) => {
    ///         admin.write_config(config);
    ///         ()
    ///     },
    ///     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
    ///     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    pub fn write_config(&self, config: Config) -> Result<(), CommandError> {
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    pub fn write_config(&mut self, config: Config) -> Result<(), Error> {
        let raw_config = RawConfig::try_from(config)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_write_config(
        get_command_result(unsafe {
            nitrokey_sys::NK_write_config(
                raw_config.numlock,
                raw_config.capslock,
                raw_config.scrollock,
                raw_config.user_password,
                false,
                self.temp_password.as_ptr() as *const c_char,
            ))
        }
    }

    fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> Result<(), CommandError>
    where
        C: Fn(RawOtpSlotData, *const c_char) -> c_int,
    {
        let raw_data = RawOtpSlotData::new(data)?;
        let temp_password_ptr = self.temp_password.as_ptr() as *const c_char;
        get_command_result(callback(raw_data, temp_password_ptr))
                self.temp_password_ptr(),
            )
        })
    }
}

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


@@ 325,13 337,14 @@ impl<T: Device> ConfigureOtp for Admin<T> {
                raw_data.use_enter,
                raw_data.use_token_id,
                raw_data.token_id.as_ptr(),
                temp_password_ptr,
                self.temp_password_ptr(),
            )
        })
    }

    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError> {
        self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe {
    fn write_totp_slot(&mut self, data: OtpSlotData, time_window: u16) -> Result<(), Error> {
        let raw_data = RawOtpSlotData::new(data)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_write_totp_slot(
                raw_data.number,
                raw_data.name.as_ptr(),


@@ 341,33 354,40 @@ impl<T: Device> ConfigureOtp for Admin<T> {
                raw_data.use_enter,
                raw_data.use_token_id,
                raw_data.token_id.as_ptr(),
                temp_password_ptr,
                self.temp_password_ptr(),
            )
        })
    }

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

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

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,
        }
    }

    fn temp_password_ptr(&self) -> *const c_char {
        self.temp_password.as_ptr() as *const c_char
    }
}

impl Authenticate for DeviceWrapper {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
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)


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

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


@@ 388,28 408,28 @@ impl Authenticate for DeviceWrapper {
    }
}

impl Authenticate for Pro {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
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, CommandError)> {
    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, CommandError)> {
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, CommandError)> {
    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/config.rs => src/config.rs +21 -14
@@ 1,4 1,9 @@
use crate::util::CommandError;
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::convert;

use crate::error::{Error, LibraryError};

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


@@ 30,21 35,21 @@ pub struct RawConfig {

fn config_otp_slot_to_option(value: u8) -> Option<u8> {
    if value < 3 {
        return Some(value);
        Some(value)
    } else {
        None
    }
    None
}

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



@@ 65,8 70,10 @@ impl Config {
    }
}

impl RawConfig {
    pub fn try_from(config: Config) -> Result<RawConfig, CommandError> {
impl convert::TryFrom<Config> for RawConfig {
    type Error = Error;

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

D src/device.rs => src/device.rs +0 -1367
@@ 1,1367 0,0 @@
use std::fmt;

use libc;
use nitrokey_sys;

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

/// Available Nitrokey models.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Model {
    /// The Nitrokey Storage.
    Storage,
    /// The Nitrokey Pro.
    Pro,
}

impl fmt::Display for Model {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                Model::Pro => "Pro",
                Model::Storage => "Storage",
            }
        )
    }
}

/// The access mode of a volume on the Nitrokey Storage.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VolumeMode {
    /// A read-only volume.
    ReadOnly,
    /// A read-write volume.
    ReadWrite,
}

impl fmt::Display for VolumeMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            VolumeMode::ReadOnly => f.write_str("read-only"),
            VolumeMode::ReadWrite => f.write_str("read-write"),
        }
    }
}

/// A wrapper for a Nitrokey device of unknown type.
///
/// Use the function [`connect`][] 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.
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, DeviceWrapper, User};
/// # use nitrokey::CommandError;
///
/// fn perform_user_task(device: &User<DeviceWrapper>) {}
/// fn perform_other_task(device: &DeviceWrapper) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         println!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// Device-specific commands:
///
/// ```no_run
/// use nitrokey::{DeviceWrapper, Storage};
/// # use nitrokey::CommandError;
///
/// fn perform_common_task(device: &DeviceWrapper) {}
/// fn perform_storage_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::connect()?;
/// perform_common_task(&device);
/// match device {
///     DeviceWrapper::Storage(storage) => perform_storage_task(&storage),
///     _ => (),
/// };
/// #     Ok(())
/// # }
/// ```
///
/// [`connect`]: fn.connect.html
#[derive(Debug)]
pub enum DeviceWrapper {
    /// A Nitrokey Storage device.
    Storage(Storage),
    /// A Nitrokey Pro device.
    Pro(Pro),
}

/// 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`][].
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Pro};
/// # use nitrokey::CommandError;
///
/// fn perform_user_task(device: &User<Pro>) {}
/// fn perform_other_task(device: &Pro) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::Pro::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         println!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: fn.connect.html
/// [`Pro::connect`]: #method.connect
#[derive(Debug)]
pub struct Pro {}

/// 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`][].
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Storage};
/// # use nitrokey::CommandError;
///
/// fn perform_user_task(device: &User<Storage>) {}
/// fn perform_other_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::Storage::connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         println!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: fn.connect.html
/// [`Storage::connect`]: #method.connect
#[derive(Debug)]
pub struct Storage {}

/// The status of a volume on a Nitrokey Storage device.
#[derive(Debug)]
pub struct VolumeStatus {
    /// Indicates whether the volume is read-only.
    pub read_only: bool,
    /// Indicates whether the volume is active.
    pub active: bool,
}

/// Information about the SD card in a Storage device.
#[derive(Debug)]
pub struct SdCardData {
    /// The serial number of the SD card.
    pub serial_number: u32,
    /// The size of the SD card in GB.
    pub size: u8,
    /// The year the card was manufactured, e. g. 17 for 2017.
    pub manufacturing_year: u8,
    /// The month the card was manufactured.
    pub manufacturing_month: u8,
    /// The OEM ID.
    pub oem: u16,
    /// The manufacturer ID.
    pub manufacturer: u8,
}

#[derive(Debug)]
/// Production information for a Storage device.
pub struct StorageProductionInfo {
    /// The major firmware version, e. g. 0 in v0.40.
    pub firmware_version_major: u8,
    /// The minor firmware version, e. g. 40 in v0.40.
    pub firmware_version_minor: u8,
    /// The internal firmware version.
    pub firmware_version_internal: u8,
    /// The serial number of the CPU.
    pub serial_number_cpu: u32,
    /// Information about the SD card.
    pub sd_card: SdCardData,
}

/// The status of a Nitrokey Storage device.
#[derive(Debug)]
pub struct StorageStatus {
    /// The status of the unencrypted volume.
    pub unencrypted_volume: VolumeStatus,
    /// The status of the encrypted volume.
    pub encrypted_volume: VolumeStatus,
    /// The status of the hidden volume.
    pub hidden_volume: VolumeStatus,
    /// The major firmware version, e. g. 0 in v0.40.
    pub firmware_version_major: u8,
    /// The minor firmware version, e. g. 40 in v0.40.
    pub firmware_version_minor: u8,
    /// Indicates whether the firmware is locked.
    pub firmware_locked: bool,
    /// The serial number of the SD card in the Storage stick.
    pub serial_number_sd_card: u32,
    /// The serial number of the smart card in the Storage stick.
    pub serial_number_smart_card: u32,
    /// The number of remaining login attempts for the user PIN.
    pub user_retry_count: u8,
    /// The number of remaining login attempts for the admin PIN.
    pub admin_retry_count: u8,
    /// Indicates whether a new SD card was found.
    pub new_sd_card_found: bool,
    /// Indicates whether the SD card is filled with random characters.
    pub filled_with_random: bool,
    /// Indicates whether the stick has been initialized by generating
    /// the AES keys.
    pub stick_initialized: bool,
}

/// A Nitrokey device.
///
/// 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 {
    /// Returns the model of the connected Nitrokey device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// println!("Connected to a Nitrokey {}", device.get_model());
    /// #    Ok(())
    /// # }
    fn get_model(&self) -> Model;

    /// Returns the serial number of the Nitrokey device.  The serial number is the string
    /// representation of a hex number.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.get_serial_number() {
    ///     Ok(number) => println!("serial no: {}", number),
    ///     Err(err) => println!("Could not get serial number: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    fn get_serial_number(&self) -> Result<String, CommandError> {
        unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) }
    }

    /// Returns the number of remaining authentication attempts for the user.  The total number of
    /// available attempts is three.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// let count = device.get_user_retry_count();
    /// println!("{} remaining authentication attempts (user)", count);
    /// #     Ok(())
    /// # }
    /// ```
    fn get_user_retry_count(&self) -> u8 {
        unsafe { nitrokey_sys::NK_get_user_retry_count() }
    }

    /// Returns the number of remaining authentication attempts for the admin.  The total number of
    /// available attempts is three.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// let count = device.get_admin_retry_count();
    /// println!("{} remaining authentication attempts (admin)", count);
    /// #     Ok(())
    /// # }
    /// ```
    fn get_admin_retry_count(&self) -> u8 {
        unsafe { nitrokey_sys::NK_get_admin_retry_count() }
    }

    /// Returns the major part of the firmware version (should be zero).
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// println!(
    ///     "Firmware version: {}.{}",
    ///     device.get_major_firmware_version(),
    ///     device.get_minor_firmware_version(),
    /// );
    /// #     Ok(())
    /// # }
    /// ```
    fn get_major_firmware_version(&self) -> i32 {
        unsafe { nitrokey_sys::NK_get_major_firmware_version() }
    }

    /// Returns the minor part of the firmware version (for example 8 for version 0.8).
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// println!(
    ///     "Firmware version: {}.{}",
    ///     device.get_major_firmware_version(),
    ///     device.get_minor_firmware_version(),
    /// );
    /// #     Ok(())
    /// # }
    fn get_minor_firmware_version(&self) -> i32 {
        unsafe { nitrokey_sys::NK_get_minor_firmware_version() }
    }

    /// Returns the current configuration of the Nitrokey device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// let config = device.get_config()?;
    /// println!("numlock binding:          {:?}", config.numlock);
    /// println!("capslock binding:         {:?}", config.capslock);
    /// println!("scrollock binding:        {:?}", config.scrollock);
    /// println!("require password for OTP: {:?}", config.user_password);
    /// #     Ok(())
    /// # }
    /// ```
    fn get_config(&self) -> Result<Config, CommandError> {
        unsafe {
            let config_ptr = nitrokey_sys::NK_read_config();
            if config_ptr.is_null() {
                return Err(get_last_error());
            }
            let config_array_ptr = config_ptr as *const [u8; 5];
            let raw_config = RawConfig::from(*config_array_ptr);
            libc::free(config_ptr as *mut libc::c_void);
            return Ok(raw_config.into());
        }
    }

    /// Changes the administrator PIN.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.change_admin_pin("12345678", "12345679") {
    ///     Ok(()) => println!("Updated admin PIN."),
    ///     Err(err) => println!("Failed to update admin PIN: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_change_admin_PIN(
                current_string.as_ptr(),
                new_string.as_ptr(),
            ))
        }
    }

    /// Changes the user PIN.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current user password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.change_user_pin("123456", "123457") {
    ///     Ok(()) => println!("Updated admin PIN."),
    ///     Err(err) => println!("Failed to update admin PIN: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_user_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_change_user_PIN(
                current_string.as_ptr(),
                new_string.as_ptr(),
            ))
        }
    }

    /// Unlocks the user PIN after three failed login attempts and sets it to the given value.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.unlock_user_pin("12345678", "123456") {
    ///     Ok(()) => println!("Unlocked user PIN."),
    ///     Err(err) => println!("Failed to unlock user PIN: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), CommandError> {
        let admin_pin_string = get_cstring(admin_pin)?;
        let user_pin_string = get_cstring(user_pin)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_unlock_user_password(
                admin_pin_string.as_ptr(),
                user_pin_string.as_ptr(),
            ))
        }
    }

    /// Locks the Nitrokey device.
    ///
    /// This disables the password store if it has been unlocked.  On the Nitrokey Storage, this
    /// also disables the volumes if they have been enabled.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.lock() {
    ///     Ok(()) => println!("Locked the Nitrokey device."),
    ///     Err(err) => println!("Could not lock the Nitrokey device: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    fn lock(&self) -> Result<(), CommandError> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_device()) }
    }

    /// Performs a factory reset on the Nitrokey device.
    ///
    /// This commands performs a factory reset on the smart card (like the factory reset via `gpg
    /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.).
    /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the
    /// encrypted volume can be used.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.factory_reset("12345678") {
    ///     Ok(()) => println!("Performed a factory reset."),
    ///     Err(err) => println!("Could not perform a factory reset: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`build_aes_key`]: #method.build_aes_key
    fn factory_reset(&self, admin_pin: &str) -> Result<(), CommandError> {
        let admin_pin_string = get_cstring(admin_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr())) }
    }

    /// Builds a new AES key on the Nitrokey.
    ///
    /// The AES key is used to encrypt the password safe and the encrypted volume.  You may need
    /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg
    /// --card-edit`.  You can also use it to destroy the data stored in the password safe or on
    /// the encrypted volume.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// match device.build_aes_key("12345678") {
    ///     Ok(()) => println!("New AES keys have been built."),
    ///     Err(err) => println!("Could not build new AES keys: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`factory_reset`]: #method.factory_reset
    fn build_aes_key(&self, admin_pin: &str) -> Result<(), CommandError> {
        let admin_pin_string = get_cstring(admin_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr())) }
    }
}

/// 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
///
/// - [`Undefined`][] 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) => println!("Could not connect to a Nitrokey: {}", err),
/// }
/// ```
///
/// [`Undefined`]: enum.CommandError.html#variant.Undefined
pub fn connect() -> Result<DeviceWrapper, CommandError> {
    unsafe {
        match nitrokey_sys::NK_login_auto() {
            1 => match get_connected_device() {
                Some(wrapper) => Ok(wrapper),
                None => Err(CommandError::Undefined),
            },
            _ => Err(CommandError::Undefined),
        }
    }
}

/// Connects to a Nitrokey device of the given model.
///
/// # Errors
///
/// - [`Undefined`][] 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) => println!("Could not connect to a Nitrokey Pro: {}", err),
/// }
/// ```
///
/// [`Undefined`]: enum.CommandError.html#variant.Undefined
pub fn connect_model(model: Model) -> Result<DeviceWrapper, CommandError> {
    if connect_enum(model) {
        Ok(create_device_wrapper(model))
    } else {
        Err(CommandError::Undefined)
    }
}

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

fn create_device_wrapper(model: Model) -> DeviceWrapper {
    match model {
        Model::Pro => DeviceWrapper::Pro(Pro {}),
        Model::Storage => DeviceWrapper::Storage(Storage {}),
    }
}

fn get_connected_device() -> Option<DeviceWrapper> {
    get_connected_model().map(create_device_wrapper)
}

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,
    };
    unsafe { nitrokey_sys::NK_login_enum(model) == 1 }
}

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

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

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

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

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

impl Device for DeviceWrapper {
    fn get_model(&self) -> Model {
        match *self {
            DeviceWrapper::Pro(_) => Model::Pro,
            DeviceWrapper::Storage(_) => Model::Storage,
        }
    }
}

impl Pro {
    /// Connects to a Nitrokey Pro.
    ///
    /// # Errors
    ///
    /// - [`Undefined`][] 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) => println!("Could not connect to the Nitrokey Pro: {}", err),
    /// }
    /// ```
    ///
    /// [`Undefined`]: enum.CommandError.html#variant.Undefined
    pub fn connect() -> Result<Pro, CommandError> {
        // TODO: maybe Option instead of Result?
        match connect_enum(Model::Pro) {
            true => Ok(Pro {}),
            false => Err(CommandError::Undefined),
        }
    }
}

impl Drop for Pro {
    fn drop(&mut self) {
        unsafe {
            nitrokey_sys::NK_logout();
        }
    }
}

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

impl GenerateOtp for Pro {}

impl Storage {
    /// Connects to a Nitrokey Storage.
    ///
    /// # Errors
    ///
    /// - [`Undefined`][] 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) => println!("Could not connect to the Nitrokey Storage: {}", err),
    /// }
    /// ```
    ///
    /// [`Undefined`]: enum.CommandError.html#variant.Undefined
    pub fn connect() -> Result<Storage, CommandError> {
        // TODO: maybe Option instead of Result?
        match connect_enum(Model::Storage) {
            true => Ok(Storage {}),
            false => Err(CommandError::Undefined),
        }
    }

    /// Changes the update PIN.
    ///
    /// The update PIN is used to enable firmware updates.  Unlike the user and the admin PIN, the
    /// update PIN is not managed by the OpenPGP smart card but by the Nitrokey firmware.  There is
    /// no retry counter as with the other PIN types.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current update password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.change_update_pin("12345678", "87654321") {
    ///     Ok(()) => println!("Updated update PIN."),
    ///     Err(err) => println!("Failed to update update PIN: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), CommandError> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_change_update_password(
                current_string.as_ptr(),
                new_string.as_ptr(),
            ))
        }
    }

    /// Enables the firmware update mode.
    ///
    /// During firmware update mode, the Nitrokey can no longer be accessed using HID commands.
    /// To resume normal operation, run `dfu-programmer at32uc3a3256s launch`.  In order to enter
    /// the firmware update mode, you need the update password that can be changed using the
    /// [`change_update_pin`][] method.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current update password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_firmware_update("12345678") {
    ///     Ok(()) => println!("Nitrokey entered update mode."),
    ///     Err(err) => println!("Could not enter update mode: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), CommandError> {
        let update_pin_string = get_cstring(update_pin)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_enable_firmware_update(
                update_pin_string.as_ptr(),
            ))
        }
    }

    /// Enables the encrypted storage volume.
    ///
    /// Once the encrypted volume is enabled, it is presented to the operating system as a block
    /// device.  The API does not provide any information on the name or path of this block device.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided user password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => println!("Enabled the encrypted volume."),
    ///     Err(err) => println!("Could not enable the encrypted volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), CommandError> {
        let user_pin = get_cstring(user_pin)?;
        unsafe { get_command_result(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) }
    }

    /// Disables the encrypted storage volume.
    ///
    /// Once the volume is disabled, it can be no longer accessed as a block device.  If the
    /// encrypted volume has not been enabled, this method still returns a success.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => {
    ///         println!("Enabled the encrypted volume.");
    ///         use_volume();
    ///         match device.disable_encrypted_volume() {
    ///             Ok(()) => println!("Disabled the encrypted volume."),
    ///             Err(err) => {
    ///                 println!("Could not disable the encrypted volume: {}", err);
    ///             },
    ///         };
    ///     },
    ///     Err(err) => println!("Could not enable the encrypted volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_encrypted_volume(&self) -> Result<(), CommandError> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_encrypted_volume()) }
    }

    /// Enables a hidden storage volume.
    ///
    /// This function will only succeed if the encrypted storage ([`enable_encrypted_volume`][]) or
    /// another hidden volume has been enabled previously.  Once the hidden volume is enabled, it
    /// is presented to the operating system as a block device and any previously opened encrypted
    /// or hidden volumes are closed.  The API does not provide any information on the name or path
    /// of this block device.
    ///
    /// Note that the encrypted and the hidden volumes operate on the same storage area, so using
    /// both at the same time might lead to data loss.
    ///
    /// The hidden volume to unlock is selected based on the provided password.
    ///
    /// # Errors
    ///
    /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling
    ///   this method or the AES key has not been built
    /// - [`InvalidString`][] if the provided password contains a null byte
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {
    ///     Ok(()) => println!("Enabled a hidden volume."),
    ///     Err(err) => println!("Could not enable the hidden volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), CommandError> {
        let volume_password = get_cstring(volume_password)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_unlock_hidden_volume(
                volume_password.as_ptr(),
            ))
        }
    }

    /// Disables a hidden storage volume.
    ///
    /// Once the volume is disabled, it can be no longer accessed as a block device.  If no hidden
    /// volume has been enabled, this method still returns a success.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// match device.enable_hidden_volume("hidden-pw") {
    ///     Ok(()) => {
    ///         println!("Enabled the hidden volume.");
    ///         use_volume();
    ///         match device.disable_hidden_volume() {
    ///             Ok(()) => println!("Disabled the hidden volume."),
    ///             Err(err) => {
    ///                 println!("Could not disable the hidden volume: {}", err);
    ///             },
    ///         };
    ///     },
    ///     Err(err) => println!("Could not enable the hidden volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_hidden_volume(&self) -> Result<(), CommandError> {
        unsafe { get_command_result(nitrokey_sys::NK_lock_hidden_volume()) }
    }

    /// Creates a hidden volume.
    ///
    /// The volume is crated in the given slot and in the given range of the available memory,
    /// where `start` is the start position as a percentage of the available memory, and `end` is
    /// the end position as a percentage of the available memory.  The volume will be protected by
    /// the given password.
    ///
    /// Note that the encrypted and the hidden volumes operate on the same storage area, so using
    /// both at the same time might lead to data loss.
    ///
    /// According to the libnitrokey documentation, this function only works if the encrypted
    /// storage has been opened.
    ///
    /// # Errors
    ///
    /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling
    ///   this method or the AES key has not been built
    /// - [`InvalidString`][] if the provided password contains a null byte
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// device.enable_encrypted_volume("123445")?;
    /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?;
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    pub fn create_hidden_volume(
        &self,
        slot: u8,
        start: u8,
        end: u8,
        password: &str,
    ) -> Result<(), CommandError> {
        let password = get_cstring(password)?;
        unsafe {
            get_command_result(nitrokey_sys::NK_create_hidden_volume(
                slot,
                start,
                end,
                password.as_ptr(),
            ))
        }
    }

    /// Sets the access mode of the unencrypted volume.
    ///
    /// This command will reconnect the unencrypted volume so buffers should be flushed before
    /// calling it.  Since firmware version v0.51, this command requires the admin PIN.  Older
    /// firmware versions are not supported.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) {
    ///     Ok(()) => println!("Set the unencrypted volume to read-write mode."),
    ///     Err(err) => println!("Could not set the unencrypted volume to read-write mode: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn set_unencrypted_volume_mode(
        &self,
        admin_pin: &str,
        mode: VolumeMode,
    ) -> Result<(), CommandError> {
        let admin_pin = get_cstring(admin_pin)?;
        let result = match mode {
            VolumeMode::ReadOnly => unsafe {
                nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr())
            },
            VolumeMode::ReadWrite => unsafe {
                nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr())
            },
        };
        get_command_result(result)
    }

    /// Returns the status of the connected storage device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.get_status() {
    ///     Ok(status) => {
    ///         println!("SD card ID: {:#x}", status.serial_number_sd_card);
    ///     },
    ///     Err(err) => println!("Could not get Storage status: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_status(&self) -> Result<StorageStatus, CommandError> {
        let mut raw_status = nitrokey_sys::NK_storage_status {
            unencrypted_volume_read_only: false,
            unencrypted_volume_active: false,
            encrypted_volume_read_only: false,
            encrypted_volume_active: false,
            hidden_volume_read_only: false,
            hidden_volume_active: false,
            firmware_version_major: 0,
            firmware_version_minor: 0,
            firmware_locked: false,
            serial_number_sd_card: 0,
            serial_number_smart_card: 0,
            user_retry_count: 0,
            admin_retry_count: 0,
            new_sd_card_found: false,
            filled_with_random: false,
            stick_initialized: false,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) };
        let result = get_command_result(raw_result);
        result.and(Ok(StorageStatus::from(raw_status)))
    }

    /// Returns the production information for the connected storage device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.get_production_info() {
    ///     Ok(data) => {
    ///         println!("SD card ID:   {:#x}", data.sd_card.serial_number);
    ///         println!("SD card size: {} GB", data.sd_card.size);
    ///     },
    ///     Err(err) => println!("Could not get Storage production info: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_production_info(&self) -> Result<StorageProductionInfo, CommandError> {
        let mut raw_data = nitrokey_sys::NK_storage_ProductionTest {
            FirmwareVersion_au8: [0, 2],
            FirmwareVersionInternal_u8: 0,
            SD_Card_Size_u8: 0,
            CPU_CardID_u32: 0,
            SmartCardID_u32: 0,
            SD_CardID_u32: 0,
            SC_UserPwRetryCount: 0,
            SC_AdminPwRetryCount: 0,
            SD_Card_ManufacturingYear_u8: 0,
            SD_Card_ManufacturingMonth_u8: 0,
            SD_Card_OEM_u16: 0,
            SD_WriteSpeed_u16: 0,
            SD_Card_Manufacturer_u8: 0,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) };
        let result = get_command_result(raw_result);
        result.and(Ok(StorageProductionInfo::from(raw_data)))
    }

    /// Clears the warning for a new SD card.
    ///
    /// The Storage status contains a field for a new SD card warning.  After a factory reset, the
    /// field is set to true.  After filling the SD card with random data, it is set to false.
    /// This method can be used to set it to false without filling the SD card with random data.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::CommandError;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::Storage::connect()?;
    /// match device.clear_new_sd_card_warning("12345678") {
    ///     Ok(()) => println!("Cleared the new SD card warning."),
    ///     Err(err) => println!("Could not set the clear the new SD card warning: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), CommandError> {
        let admin_pin = get_cstring(admin_pin)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())
        })
    }

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

    /// Exports the firmware to the unencrypted volume.
    ///
    /// This command requires the admin PIN.  The unencrypted volume must be in read-write mode
    /// when this command is executed.  Otherwise, it will still return `Ok` but not write the
    /// firmware.
    ///
    /// This command unmounts the unencrypted volume if it has been mounted, so all buffers should
    /// be flushed.  The firmware is written to the `firmware.bin` file on the unencrypted volume.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn export_firmware(&self, admin_pin: &str) -> Result<(), CommandError> {
        let admin_pin_string = get_cstring(admin_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) })
    }
}

impl Drop for Storage {
    fn drop(&mut self) {
        unsafe {
            nitrokey_sys::NK_logout();
        }
    }
}

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

impl GenerateOtp for Storage {}

impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {
    fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {
        Self {
            firmware_version_major: data.FirmwareVersion_au8[0],
            firmware_version_minor: data.FirmwareVersion_au8[1],
            firmware_version_internal: data.FirmwareVersionInternal_u8,
            serial_number_cpu: data.CPU_CardID_u32,
            sd_card: SdCardData {
                serial_number: data.SD_CardID_u32,
                size: data.SD_Card_Size_u8,
                manufacturing_year: data.SD_Card_ManufacturingYear_u8,
                manufacturing_month: data.SD_Card_ManufacturingMonth_u8,
                oem: data.SD_Card_OEM_u16,
                manufacturer: data.SD_Card_Manufacturer_u8,
            },
        }
    }
}

impl From<nitrokey_sys::NK_storage_status> for StorageStatus {
    fn from(status: nitrokey_sys::NK_storage_status) -> Self {
        StorageStatus {
            unencrypted_volume: VolumeStatus {
                read_only: status.unencrypted_volume_read_only,
                active: status.unencrypted_volume_active,
            },
            encrypted_volume: VolumeStatus {
                read_only: status.encrypted_volume_read_only,
                active: status.encrypted_volume_active,
            },
            hidden_volume: VolumeStatus {
                read_only: status.hidden_volume_read_only,
                active: status.hidden_volume_active,
            },
            firmware_version_major: status.firmware_version_major,
            firmware_version_minor: status.firmware_version_minor,
            firmware_locked: status.firmware_locked,
            serial_number_sd_card: status.serial_number_sd_card,
            serial_number_smart_card: status.serial_number_smart_card,
            user_retry_count: status.user_retry_count,
            admin_retry_count: status.admin_retry_count,
            new_sd_card_found: status.new_sd_card_found,
            filled_with_random: status.filled_with_random,
            stick_initialized: status.stick_initialized,
        }
    }
}

A src/device/mod.rs => src/device/mod.rs +464 -0
@@ 0,0 1,464 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

mod pro;
mod storage;
mod wrapper;

use std::fmt;

use libc;
use nitrokey_sys;

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

pub use pro::Pro;
pub use storage::{
    SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
};
pub use wrapper::DeviceWrapper;

/// Available Nitrokey models.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Model {
    /// The Nitrokey Storage.
    Storage,
    /// The Nitrokey Pro.
    Pro,
}

impl fmt::Display for Model {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            Model::Pro => "Pro",
            Model::Storage => "Storage",
        })
    }
}

/// A firmware version for a Nitrokey device.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FirmwareVersion {
    /// The major firmware version, e. g. 0 in v0.40.
    pub major: u8,
    /// The minor firmware version, e. g. 40 in v0.40.
    pub minor: u8,
}

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

/// A Nitrokey device.
///
/// This trait provides the commands that can be executed without authentication and that are
/// present on all supported Nitrokey devices.
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
    ///     // ...
    /// }
    ///
    /// match nitrokey::take()?.connect() {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    fn into_manager(self) -> &'a mut crate::Manager;

    /// Returns the model of the connected Nitrokey device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// println!("Connected to a Nitrokey {}", device.get_model());
    /// #    Ok(())
    /// # }
    fn get_model(&self) -> Model;

    /// Returns the serial number of the Nitrokey device.  The serial number is the string
    /// representation of a hex number.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    fn get_serial_number(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() })
    }

    /// Returns the number of remaining authentication attempts for the user.  The total number of
    /// available attempts is three.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    fn get_user_retry_count(&self) -> Result<u8, Error> {
        result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
    }

    /// Returns the number of remaining authentication attempts for the admin.  The total number of
    /// available attempts is three.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    ///     Err(err) => eprintln!("Could not get admin retry count: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    fn get_admin_retry_count(&self) -> Result<u8, Error> {
        result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
    }

    /// Returns the firmware version.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> {
        let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?;
        let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?;
        Ok(FirmwareVersion { major, minor })
    }

    /// Returns the current configuration of the Nitrokey device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// let config = device.get_config()?;
    /// println!("numlock binding:          {:?}", config.numlock);
    /// println!("capslock binding:         {:?}", config.capslock);
    /// println!("scrollock binding:        {:?}", config.scrollock);
    /// println!("require password for OTP: {:?}", config.user_password);
    /// #     Ok(())
    /// # }
    /// ```
    fn get_config(&self) -> Result<Config, Error> {
        let config_ptr = unsafe { nitrokey_sys::NK_read_config() };
        if config_ptr.is_null() {
            return Err(get_last_error());
        }
        let config_array_ptr = config_ptr as *const [u8; 5];
        let raw_config = unsafe { RawConfig::from(*config_array_ptr) };
        unsafe { libc::free(config_ptr as *mut libc::c_void) };
        Ok(raw_config.into())
    }

    /// Changes the administrator PIN.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_admin_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_change_admin_PIN(current_string.as_ptr(), new_string.as_ptr())
        })
    }

    /// Changes the user PIN.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current user password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn change_user_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr())
        })
    }

    /// Unlocks the user PIN after three failed login attempts and sets it to the given value.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn unlock_user_pin(&mut self, admin_pin: &str, user_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        let user_pin_string = get_cstring(user_pin)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_unlock_user_password(
                admin_pin_string.as_ptr(),
                user_pin_string.as_ptr(),
            )
        })
    }

    /// Locks the Nitrokey device.
    ///
    /// This disables the password store if it has been unlocked.  On the Nitrokey Storage, this
    /// also disables the volumes if they have been enabled.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    fn lock(&mut self) -> Result<(), Error> {
        get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
    }

    /// Performs a factory reset on the Nitrokey device.
    ///
    /// This commands performs a factory reset on the smart card (like the factory reset via `gpg
    /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.).
    /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the
    /// encrypted volume can be used.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`build_aes_key`]: #method.build_aes_key
    fn factory_reset(&mut self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) })
    }

    /// Builds a new AES key on the Nitrokey.
    ///
    /// The AES key is used to encrypt the password safe and the encrypted volume.  You may need
    /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg
    /// --card-edit`.  You can also use it to destroy the data stored in the password safe or on
    /// the encrypted volume.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::Device;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`factory_reset`]: #method.factory_reset
    fn build_aes_key(&mut self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) })
    }
}

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

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

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()),
    }
}

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,
    };
    unsafe { nitrokey_sys::NK_login_enum(model) == 1 }
}

A src/device/pro.rs => src/device/pro.rs +79 -0
@@ 0,0 1,79 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use nitrokey_sys;

use crate::device::{Device, Model};
use crate::otp::GenerateOtp;

/// A Nitrokey Pro device without user or admin authentication.
///
/// 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
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Pro};
/// # use nitrokey::Error;
///
/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {}
/// fn perform_other_task(device: &Pro) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_pro()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         eprintln!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: struct.Manager.html#method.connect
/// [`connect_pro`]: struct.Manager.html#method.connect_pro
#[derive(Debug)]
pub struct Pro<'a> {
    manager: Option<&'a mut crate::Manager>,
}

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

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

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<'a> GenerateOtp for Pro<'a> {}

A src/device/storage.rs => src/device/storage.rs +735 -0
@@ 0,0 1,735 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::fmt;

use nitrokey_sys;

use crate::device::{Device, FirmwareVersion, Model};
use crate::error::Error;
use crate::otp::GenerateOtp;
use crate::util::{get_command_result, get_cstring};

/// A Nitrokey Storage device without user or admin authentication.
///
/// 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
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Storage};
/// # use nitrokey::Error;
///
/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {}
/// fn perform_other_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_storage()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         eprintln!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: struct.Manager.html#method.connect
/// [`connect_storage`]: struct.Manager.html#method.connect_storage
#[derive(Debug)]
pub struct Storage<'a> {
    manager: Option<&'a mut crate::Manager>,
}

/// The access mode of a volume on the Nitrokey Storage.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VolumeMode {
    /// A read-only volume.
    ReadOnly,
    /// A read-write volume.
    ReadWrite,
}

impl fmt::Display for VolumeMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            VolumeMode::ReadOnly => "read-only",
            VolumeMode::ReadWrite => "read-write",
        })
    }
}

/// The status of a volume on a Nitrokey Storage device.
#[derive(Debug)]
pub struct VolumeStatus {
    /// Indicates whether the volume is read-only.
    pub read_only: bool,
    /// Indicates whether the volume is active.
    pub active: bool,
}

/// Information about the SD card in a Storage device.
#[derive(Debug)]
pub struct SdCardData {
    /// The serial number of the SD card.
    pub serial_number: u32,
    /// The size of the SD card in GB.
    pub size: u8,
    /// The year the card was manufactured, e. g. 17 for 2017.
    pub manufacturing_year: u8,
    /// The month the card was manufactured.
    pub manufacturing_month: u8,
    /// The OEM ID.
    pub oem: u16,
    /// The manufacturer ID.
    pub manufacturer: u8,
}

/// Production information for a Storage device.
#[derive(Debug)]
pub struct StorageProductionInfo {
    /// The firmware version.
    pub firmware_version: FirmwareVersion,
    /// The internal firmware version.
    pub firmware_version_internal: u8,
    /// The serial number of the CPU.
    pub serial_number_cpu: u32,
    /// Information about the SD card.
    pub sd_card: SdCardData,
}

/// The status of a Nitrokey Storage device.
#[derive(Debug)]
pub struct StorageStatus {
    /// The status of the unencrypted volume.
    pub unencrypted_volume: VolumeStatus,
    /// The status of the encrypted volume.
    pub encrypted_volume: VolumeStatus,
    /// The status of the hidden volume.
    pub hidden_volume: VolumeStatus,
    /// The firmware version.
    pub firmware_version: FirmwareVersion,
    /// Indicates whether the firmware is locked.
    pub firmware_locked: bool,
    /// The serial number of the SD card in the Storage stick.
    pub serial_number_sd_card: u32,
    /// The serial number of the smart card in the Storage stick.
    pub serial_number_smart_card: u32,
    /// The number of remaining login attempts for the user PIN.
    pub user_retry_count: u8,
    /// The number of remaining login attempts for the admin PIN.
    pub admin_retry_count: u8,
    /// Indicates whether a new SD card was found.
    pub new_sd_card_found: bool,
    /// Indicates whether the SD card is filled with random characters.
    pub filled_with_random: bool,
    /// Indicates whether the stick has been initialized by generating
    /// the AES keys.
    pub stick_initialized: bool,
}

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

    /// Changes the update PIN.
    ///
    /// The update PIN is used to enable firmware updates.  Unlike the user and the admin PIN, the
    /// update PIN is not managed by the OpenPGP smart card but by the Nitrokey firmware.  There is
    /// no retry counter as with the other PIN types.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current update password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn change_update_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
        let current_string = get_cstring(current)?;
        let new_string = get_cstring(new)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_change_update_password(current_string.as_ptr(), new_string.as_ptr())
        })
    }

    /// Enables the firmware update mode.
    ///
    /// During firmware update mode, the Nitrokey can no longer be accessed using HID commands.
    /// To resume normal operation, run `dfu-programmer at32uc3a3256s launch`.  In order to enter
    /// the firmware update mode, you need the update password that can be changed using the
    /// [`change_update_pin`][] method.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the current update password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_firmware_update(&mut self, update_pin: &str) -> Result<(), Error> {
        let update_pin_string = get_cstring(update_pin)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_enable_firmware_update(update_pin_string.as_ptr())
        })
    }

    /// Enables the encrypted storage volume.
    ///
    /// Once the encrypted volume is enabled, it is presented to the operating system as a block
    /// device.  The API does not provide any information on the name or path of this block device.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided user password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn enable_encrypted_volume(&mut self, user_pin: &str) -> Result<(), Error> {
        let user_pin = get_cstring(user_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr()) })
    }

    /// Disables the encrypted storage volume.
    ///
    /// Once the volume is disabled, it can be no longer accessed as a block device.  If the
    /// encrypted volume has not been enabled, this method still returns a success.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect_storage()?;
    /// match device.enable_encrypted_volume("123456") {
    ///     Ok(()) => {
    ///         println!("Enabled the encrypted volume.");
    ///         use_volume();
    ///         match device.disable_encrypted_volume() {
    ///             Ok(()) => println!("Disabled the encrypted volume."),
    ///             Err(err) => {
    ///                 eprintln!("Could not disable the encrypted volume: {}", err);
    ///             },
    ///         };
    ///     },
    ///     Err(err) => eprintln!("Could not enable the encrypted volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> {
        get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() })
    }

    /// Enables a hidden storage volume.
    ///
    /// This function will only succeed if the encrypted storage ([`enable_encrypted_volume`][]) or
    /// another hidden volume has been enabled previously.  Once the hidden volume is enabled, it
    /// is presented to the operating system as a block device and any previously opened encrypted
    /// or hidden volumes are closed.  The API does not provide any information on the name or path
    /// of this block device.
    ///
    /// Note that the encrypted and the hidden volumes operate on the same storage area, so using
    /// both at the same time might lead to data loss.
    ///
    /// The hidden volume to unlock is selected based on the provided password.
    ///
    /// # Errors
    ///
    /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling
    ///   this method or the AES key has not been built
    /// - [`InvalidString`][] if the provided password contains a null byte
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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."),
    ///     Err(err) => eprintln!("Could not enable the hidden volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    pub fn enable_hidden_volume(&mut self, volume_password: &str) -> Result<(), Error> {
        let volume_password = get_cstring(volume_password)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_unlock_hidden_volume(volume_password.as_ptr())
        })
    }

    /// Disables a hidden storage volume.
    ///
    /// Once the volume is disabled, it can be no longer accessed as a block device.  If no hidden
    /// volume has been enabled, this method still returns a success.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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 the hidden volume.");
    ///         use_volume();
    ///         match device.disable_hidden_volume() {
    ///             Ok(()) => println!("Disabled the hidden volume."),
    ///             Err(err) => {
    ///                 eprintln!("Could not disable the hidden volume: {}", err);
    ///             },
    ///         };
    ///     },
    ///     Err(err) => eprintln!("Could not enable the hidden volume: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn disable_hidden_volume(&mut self) -> Result<(), Error> {
        get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() })
    }

    /// Creates a hidden volume.
    ///
    /// The volume is crated in the given slot and in the given range of the available memory,
    /// where `start` is the start position as a percentage of the available memory, and `end` is
    /// the end position as a percentage of the available memory.  The volume will be protected by
    /// the given password.
    ///
    /// Note that the encrypted and the hidden volumes operate on the same storage area, so using
    /// both at the same time might lead to data loss.
    ///
    /// According to the libnitrokey documentation, this function only works if the encrypted
    /// storage has been opened.
    ///
    /// # Errors
    ///
    /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling
    ///   this method or the AES key has not been built
    /// - [`InvalidString`][] if the provided password contains a null byte
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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(())
    /// # }
    /// ```
    ///
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    pub fn create_hidden_volume(
        &mut self,
        slot: u8,
        start: u8,
        end: u8,
        password: &str,
    ) -> Result<(), Error> {
        let password = get_cstring(password)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_create_hidden_volume(slot, start, end, password.as_ptr())
        })
    }

    /// Sets the access mode of the unencrypted volume.
    ///
    /// This command will reconnect the unencrypted volume so buffers should be flushed before
    /// calling it.  Since firmware version v0.51, this command requires the admin PIN.  Older
    /// firmware versions are not supported.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn set_unencrypted_volume_mode(
        &mut self,
        admin_pin: &str,
        mode: VolumeMode,
    ) -> Result<(), Error> {
        let admin_pin = get_cstring(admin_pin)?;
        let result = match mode {
            VolumeMode::ReadOnly => unsafe {
                nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr())
            },
            VolumeMode::ReadWrite => unsafe {
                nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr())
            },
        };
        get_command_result(result)
    }

    /// Sets the access mode of the encrypted volume.
    ///
    /// This command will reconnect the encrypted volume so buffers should be flushed before
    /// calling it.  It is only available in firmware version 0.49.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    /// use nitrokey::VolumeMode;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn set_encrypted_volume_mode(
        &mut self,
        admin_pin: &str,
        mode: VolumeMode,
    ) -> Result<(), Error> {
        let admin_pin = get_cstring(admin_pin)?;
        let result = match mode {
            VolumeMode::ReadOnly => unsafe {
                nitrokey_sys::NK_set_encrypted_read_only(admin_pin.as_ptr())
            },
            VolumeMode::ReadWrite => unsafe {
                nitrokey_sys::NK_set_encrypted_read_write(admin_pin.as_ptr())
            },
        };
        get_command_result(result)
    }

    /// Returns the status of the connected storage device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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);
    ///     },
    ///     Err(err) => eprintln!("Could not get Storage status: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_status(&self) -> Result<StorageStatus, Error> {
        let mut raw_status = nitrokey_sys::NK_storage_status {
            unencrypted_volume_read_only: false,
            unencrypted_volume_active: false,
            encrypted_volume_read_only: false,
            encrypted_volume_active: false,
            hidden_volume_read_only: false,
            hidden_volume_active: false,
            firmware_version_major: 0,
            firmware_version_minor: 0,
            firmware_locked: false,
            serial_number_sd_card: 0,
            serial_number_smart_card: 0,
            user_retry_count: 0,
            admin_retry_count: 0,
            new_sd_card_found: false,
            filled_with_random: false,
            stick_initialized: false,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) };
        get_command_result(raw_result).map(|_| StorageStatus::from(raw_status))
    }

    /// Returns the production information for the connected storage device.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// fn use_volume() {}
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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);
    ///         println!("SD card size: {} GB", data.sd_card.size);
    ///     },
    ///     Err(err) => eprintln!("Could not get Storage production info: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    pub fn get_production_info(&self) -> Result<StorageProductionInfo, Error> {
        let mut raw_data = nitrokey_sys::NK_storage_ProductionTest {
            FirmwareVersion_au8: [0, 2],
            FirmwareVersionInternal_u8: 0,
            SD_Card_Size_u8: 0,
            CPU_CardID_u32: 0,
            SmartCardID_u32: 0,
            SD_CardID_u32: 0,
            SC_UserPwRetryCount: 0,
            SC_AdminPwRetryCount: 0,
            SD_Card_ManufacturingYear_u8: 0,
            SD_Card_ManufacturingMonth_u8: 0,
            SD_Card_OEM_u16: 0,
            SD_WriteSpeed_u16: 0,
            SD_Card_Manufacturer_u8: 0,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) };
        get_command_result(raw_result).map(|_| StorageProductionInfo::from(raw_data))
    }

    /// Clears the warning for a new SD card.
    ///
    /// The Storage status contains a field for a new SD card warning.  After a factory reset, the
    /// field is set to true.  After filling the SD card with random data, it is set to false.
    /// This method can be used to set it to false without filling the SD card with random data.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if the provided password contains a null byte
    /// - [`WrongPassword`][] if the provided admin password is wrong
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// 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),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn clear_new_sd_card_warning(&mut self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin = get_cstring(admin_pin)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())
        })
    }

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

    /// Exports the firmware to the unencrypted volume.
    ///
    /// This command requires the admin PIN.  The unencrypted volume must be in read-write mode
    /// when this command is executed.  Otherwise, it will still return `Ok` but not write the
    /// firmware.
    ///
    /// This command unmounts the unencrypted volume if it has been mounted, so all buffers should
    /// be flushed.  The firmware is written to the `firmware.bin` file on the unencrypted volume.
    ///
    /// # Errors
    ///
    /// - [`InvalidString`][] if one of the provided passwords contains a null byte
    /// - [`WrongPassword`][] if the admin password is wrong
    ///
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    pub fn export_firmware(&mut self, admin_pin: &str) -> Result<(), Error> {
        let admin_pin_string = get_cstring(admin_pin)?;
        get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) })
    }
}

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

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<'a> GenerateOtp for Storage<'a> {}

impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {
    fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {
        Self {
            firmware_version: FirmwareVersion {
                major: data.FirmwareVersion_au8[0],
                minor: data.FirmwareVersion_au8[1],
            },
            firmware_version_internal: data.FirmwareVersionInternal_u8,
            serial_number_cpu: data.CPU_CardID_u32,
            sd_card: SdCardData {
                serial_number: data.SD_CardID_u32,
                size: data.SD_Card_Size_u8,
                manufacturing_year: data.SD_Card_ManufacturingYear_u8,
                manufacturing_month: data.SD_Card_ManufacturingMonth_u8,
                oem: data.SD_Card_OEM_u16,
                manufacturer: data.SD_Card_Manufacturer_u8,
            },
        }
    }
}

impl From<nitrokey_sys::NK_storage_status> for StorageStatus {
    fn from(status: nitrokey_sys::NK_storage_status) -> Self {
        StorageStatus {
            unencrypted_volume: VolumeStatus {
                read_only: status.unencrypted_volume_read_only,
                active: status.unencrypted_volume_active,
            },
            encrypted_volume: VolumeStatus {
                read_only: status.encrypted_volume_read_only,
                active: status.encrypted_volume_active,
            },
            hidden_volume: VolumeStatus {
                read_only: status.hidden_volume_read_only,
                active: status.hidden_volume_active,
            },
            firmware_version: FirmwareVersion {
                major: status.firmware_version_major,
                minor: status.firmware_version_minor,
            },
            firmware_locked: status.firmware_locked,
            serial_number_sd_card: status.serial_number_sd_card,
            serial_number_smart_card: status.serial_number_smart_card,
            user_retry_count: status.user_retry_count,
            admin_retry_count: status.admin_retry_count,
            new_sd_card_found: status.new_sd_card_found,
            filled_with_random: status.filled_with_random,
            stick_initialized: status.stick_initialized,
        }
    }
}

A src/device/wrapper.rs => src/device/wrapper.rs +134 -0
@@ 0,0 1,134 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use crate::device::{Device, Model, Pro, Storage};
use crate::error::Error;
use crate::otp::GenerateOtp;

/// A wrapper for a Nitrokey device of unknown type.
///
/// 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.
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, DeviceWrapper, User};
/// # use nitrokey::Error;
///
/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
/// fn perform_other_task(device: &DeviceWrapper) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         eprintln!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// Device-specific commands:
///
/// ```no_run
/// use nitrokey::{DeviceWrapper, Storage};
/// # use nitrokey::Error;
///
/// fn perform_common_task(device: &DeviceWrapper) {}
/// fn perform_storage_task(device: &Storage) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect()?;
/// perform_common_task(&device);
/// match device {
///     DeviceWrapper::Storage(storage) => perform_storage_task(&storage),
///     _ => (),
/// };
/// #     Ok(())
/// # }
/// ```
///
/// [`connect`]: struct.Manager.html#method.connect
#[derive(Debug)]
pub enum DeviceWrapper<'a> {
    /// A Nitrokey Storage device.
    Storage(Storage<'a>),
    /// A Nitrokey Pro device.
    Pro(Pro<'a>),
}

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<'a> {
        match *self {
            DeviceWrapper::Storage(ref mut storage) => storage,
            DeviceWrapper::Pro(ref mut pro) => pro,
        }
    }
}

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

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

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

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

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

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

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,
            DeviceWrapper::Storage(_) => Model::Storage,
        }
    }
}

A src/error.rs => src/error.rs +269 -0
@@ 0,0 1,269 @@
// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::error;
use std::fmt;
use std::os::raw;
use std::str;
use std::sync;

use crate::device;

/// An error returned by the nitrokey crate.
#[derive(Debug)]
pub enum Error {
    /// An error reported by the Nitrokey device in the response packet.
    CommandError(CommandError),
    /// A device communication error.
    CommunicationError(CommunicationError),
    /// An error occurred due to concurrent access to the Nitrokey device.
    ConcurrentAccessError,
    /// A library usage error.
    LibraryError(LibraryError),
    /// 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,
    /// An unknown error returned by libnitrokey.
    UnknownError(i64),
    /// An error occurred when interpreting a UTF-8 string.
    Utf8Error(str::Utf8Error),
}

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

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

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

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

impl From<str::Utf8Error> for Error {
    fn from(error: str::Utf8Error) -> Self {
        Error::Utf8Error(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
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            Error::CommandError(ref err) => Some(err),
            Error::CommunicationError(ref err) => Some(err),
            Error::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,
            Error::Utf8Error(ref err) => Some(err),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Error::CommandError(ref err) => write!(f, "Command error: {}", err),
            Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err),
            Error::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),
            Error::Utf8Error(ref err) => write!(f, "UTF-8 error: {}", err),
        }
    }
}

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

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

impl error::Error for CommandError {}

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

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

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

impl error::Error for CommunicationError {}

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

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

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

impl error::Error for LibraryError {}

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

M src/lib.rs => src/lib.rs +324 -28
@@ 1,3 1,6 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

//! Provides access to a Nitrokey device using the native libnitrokey API.
//!
//! # Usage


@@ 6,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


@@ 25,10 31,11 @@
//!
//! ```no_run
//! use nitrokey::Device;
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), CommandError> {
//! let device = nitrokey::connect()?;
//! # fn try_main() -> Result<(), Error> {
//! let mut manager = nitrokey::take()?;
//! let device = manager.connect()?;
//! println!("{}", device.get_serial_number()?);
//! #     Ok(())
//! # }


@@ 38,19 45,20 @@
//!
//! ```no_run
//! use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), (CommandError)> {
//! let device = nitrokey::connect()?;
//! # fn try_main() -> Result<(), Error> {
//! 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(admin) => {
//!     Ok(mut admin) => {
//!         match admin.write_hotp_slot(slot_data, 0) {
//!             Ok(()) => println!("Successfully wrote slot."),
//!             Err(err) => println!("Could not write slot: {}", err),
//!             Err(err) => eprintln!("Could not write slot: {}", err),
//!         }
//!     },
//!     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
//!     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
//! }
//! #     Ok(())
//! # }


@@ 60,13 68,14 @@
//!
//! ```no_run
//! use nitrokey::{Device, GenerateOtp};
//! # use nitrokey::CommandError;
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), (CommandError)> {
//! let device = nitrokey::connect()?;
//! # fn try_main() -> Result<(), Error> {
//! 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) => println!("Could not generate HOTP code: {}", err),
//!     Err(err) => eprintln!("Could not generate HOTP code: {}", err),
//! }
//! #     Ok(())
//! # }


@@ 74,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


@@ 86,24 98,42 @@

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

#[macro_use(lazy_static)]
extern crate lazy_static;

mod auth;
mod config;
mod device;
mod error;
mod otp;
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;
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};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::util::{CommandError, LogLevel};
pub use crate::util::LogLevel;

/// The default admin PIN for all Nitrokey devices.
pub const DEFAULT_ADMIN_PIN: &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.
///


@@ 125,6 155,265 @@ pub struct Version {
    pub minor: u32,
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.git.is_empty() {
            write!(f, "v{}.{}", self.major, self.minor)
        } else {
            f.write_str(&self.git)
        }
    }
}

/// 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) {}
    ///
    /// let mut manager = nitrokey::take()?;
    /// match manager.connect() {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    ///
    /// [`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) {}
    ///
    /// match nitrokey::take()?.connect_model(Model::Pro) {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    ///
    /// [`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) {}
    ///
    /// match nitrokey::take()?.connect_pro() {
    ///     Ok(device) => use_pro(device),
    ///     Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    ///
    /// [`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) {}
    ///
    /// match nitrokey::take()?.connect_storage() {
    ///     Ok(device) => use_storage(device),
    ///     Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    ///
    /// [`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`][].  If you want to access the manager instance even if the cache is poisoned,
/// use [`force_take`][].
///
/// # Errors
///
/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked
/// - [`PoisonError`][] if the lock is poisoned
///
/// [`take_blocking`]: fn.take_blocking.html
/// [`force_take`]: fn.force_take.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)
}

/// Try to take an instance of the connection manager, ignoring a poisoned cache.
///
/// 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`][].
///
/// If a thread has previously panicked while accessing the manager instance, the cache is
/// poisoned.  The default implementation, [`take`][], returns a [`PoisonError`][] on subsequent
/// calls.  This implementation ignores the poisoned cache and returns the manager instance.
///
/// # Errors
///
/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked
///
/// [`take`]: fn.take.html
/// [`take_blocking`]: fn.take_blocking.html
/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError
/// [`Manager`]: struct.Manager.html
pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
    match take() {
        Ok(guard) => Ok(guard),
        Err(err) => match err {
            Error::PoisonError(err) => Ok(err.into_inner()),
            err => Err(err),
        },
    }
}

/// 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`][]).


@@ 149,21 438,28 @@ pub fn set_log_level(level: LogLevel) {

/// Returns the libnitrokey library version.
///
/// # Errors
///
/// - [`Utf8Error`][] if libnitrokey returned an invalid UTF-8 string
///
/// # Example
///
/// ```
/// let version = nitrokey::get_library_version();
/// let version = nitrokey::get_library_version()?;
/// println!("Using libnitrokey {}", version.git);
/// # Ok::<(), nitrokey::Error>(())
/// ```
pub fn get_library_version() -> Version {
///
/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
pub fn get_library_version() -> Result<Version, Error> {
    // NK_get_library_version returns a static string, so we don’t have to free the pointer.
    let git = unsafe { nitrokey_sys::NK_get_library_version() };
    let git = if git.is_null() {
        String::new()
    } else {
        util::owned_str_from_ptr(git)
        util::owned_str_from_ptr(git)?
    };
    let major = unsafe { nitrokey_sys::NK_get_major_library_version() };
    let minor = unsafe { nitrokey_sys::NK_get_minor_library_version() };
    Version { git, major, minor }
    Ok(Version { git, major, minor })
}

M src/otp.rs => src/otp.rs +83 -74
@@ 1,8 1,12 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::ffi::CString;

use nitrokey_sys;

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

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


@@ 28,28 32,29 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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(admin) => {
    ///     Ok(mut admin) => {
    ///         match admin.write_hotp_slot(slot_data, 0) {
    ///             Ok(()) => println!("Successfully wrote slot."),
    ///             Err(err) => println!("Could not write slot: {}", err),
    ///             Err(err) => eprintln!("Could not write slot: {}", err),
    ///         }
    ///     },
    ///     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
    ///     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`NoName`]: enum.CommandError.html#variant.NoName
    fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError>;
    fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error>;

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


@@ 64,28 69,29 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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(admin) => {
    ///     Ok(mut admin) => {
    ///         match admin.write_totp_slot(slot_data, 30) {
    ///             Ok(()) => println!("Successfully wrote slot."),
    ///             Err(err) => println!("Could not write slot: {}", err),
    ///             Err(err) => eprintln!("Could not write slot: {}", err),
    ///         }
    ///     },
    ///     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
    ///     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`NoName`]: enum.CommandError.html#variant.NoName
    fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError>;
    fn write_totp_slot(&mut self, data: OtpSlotData, time_window: u16) -> Result<(), Error>;

    /// Erases an HOTP slot.
    ///


@@ 97,25 103,26 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(admin) => {
    ///     Ok(mut admin) => {
    ///         match admin.erase_hotp_slot(1) {
    ///             Ok(()) => println!("Successfully erased slot."),
    ///             Err(err) => println!("Could not erase slot: {}", err),
    ///             Err(err) => eprintln!("Could not erase slot: {}", err),
    ///         }
    ///     },
    ///     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
    ///     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError>;
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    fn erase_hotp_slot(&mut self, slot: u8) -> Result<(), Error>;

    /// Erases a TOTP slot.
    ///


@@ 127,25 134,26 @@ pub trait ConfigureOtp {
    ///
    /// ```no_run
    /// use nitrokey::{Authenticate, ConfigureOtp};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), (CommandError)> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let device = manager.connect()?;
    /// match device.authenticate_admin("12345678") {
    ///     Ok(admin) => {
    ///     Ok(mut admin) => {
    ///         match admin.erase_totp_slot(1) {
    ///             Ok(()) => println!("Successfully erased slot."),
    ///             Err(err) => println!("Could not erase slot: {}", err),
    ///             Err(err) => eprintln!("Could not erase slot: {}", err),
    ///         }
    ///     },
    ///     Err((_, err)) => println!("Could not authenticate as admin: {}", err),
    ///     Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError>;
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    fn erase_totp_slot(&mut self, slot: u8) -> Result<(), Error>;
}

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


@@ 164,14 172,15 @@ pub trait GenerateOtp {
    /// ```no_run
    /// use std::time;
    /// use nitrokey::GenerateOtp;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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)?,
    ///     Err(_) => println!("The system time is before the Unix epoch!"),
    ///     Err(_) => eprintln!("The system time is before the Unix epoch!"),
    /// }
    /// #     Ok(())
    /// # }


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


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

    /// Returns the name of the given TOTP slot.


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

    /// Generates an HOTP code on the given slot.  This operation may require user authorization,


@@ 263,10 274,11 @@ pub trait GenerateOtp {
    ///
    /// ```no_run
    /// use nitrokey::GenerateOtp;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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(())


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

    /// Generates a TOTP code on the given slot.  This operation may require user authorization,


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


@@ 311,7 322,7 @@ pub trait GenerateOtp {
    ///         let code = device.get_totp_code(1)?;
    ///         println!("Generated TOTP code on slot 1: {}", code);
    ///     },
    ///     Err(_) => println!("Timestamps before 1970-01-01 are not supported!"),
    ///     Err(_) => eprintln!("Timestamps before 1970-01-01 are not supported!"),
    /// }
    /// #     Ok(())
    /// # }


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



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

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

M src/pws.rs => src/pws.rs +86 -78
@@ 1,10 1,12 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use libc;
use nitrokey_sys;

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

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


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


@@ 40,10 42,12 @@ pub const SLOT_COUNT: u8 = 16;
///     Ok(())
/// }
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::connect()?;
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let mut device = manager.connect()?;
/// let pws = device.get_password_safe("123456")?;
/// use_password_safe(&pws);
/// drop(pws);
/// device.lock()?;
/// #     Ok(())
/// # }


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

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


@@ 63,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


@@ 89,19 94,20 @@ pub trait GetPasswordSafe {
    ///
    /// ```no_run
    /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// fn use_password_safe(pws: &PasswordSafe) {}
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// match device.get_password_safe("123456") {
    ///     Ok(pws) => {
    ///         use_password_safe(&pws);
    ///         device.lock()?;
    ///     },
    ///     Err(err) => println!("Could not open the password safe: {}", err),
    ///     Err(err) => eprintln!("Could not open the password safe: {}", err),
    /// };
    /// device.lock()?;
    /// #     Ok(())
    /// # }
    /// ```


@@ 110,34 116,30 @@ pub trait GetPasswordSafe {
    /// [`lock`]: trait.Device.html#method.lock
    /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
    /// [`Device::build_aes_key`]: trait.Device.html#method.build_aes_key
    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`Unknown`]: enum.CommandError.html#variant.Unknown
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError>;
    fn get_password_safe(&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>, CommandError> {
) -> Result<PasswordSafe<'a, 'b>, Error> {
    let user_pin_string = get_cstring(user_pin)?;
    let result = unsafe {
        get_command_result(nitrokey_sys::NK_enable_password_safe(
            user_pin_string.as_ptr(),
        ))
    };
    result.map(|()| PasswordSafe { _device: device })
    get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) })
        .map(|_| PasswordSafe { _device: device })
}

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

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.


@@ 146,10 148,11 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::{GetPasswordSafe, SLOT_COUNT};
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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 {


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


@@ 191,10 194,11 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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)?;


@@ 202,16 206,16 @@ impl<'a> PasswordSafe<'a> {
    ///         let password = pws.get_slot_login(0)?;
    ///         println!("Credentials for {}: login {}, password {}", name, login, password);
    ///     },
    ///     Err(err) => println!("Could not open the password safe: {}", err),
    ///     Err(err) => eprintln!("Could not open the password safe: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_name(&self, slot: u8) -> Result<String, CommandError> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) }
    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)
    }



@@ 228,10 232,11 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # 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)?;


@@ 241,10 246,10 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_login(&self, slot: u8) -> Result<String, CommandError> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) }
    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)
    }



@@ 261,10 266,11 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # 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)?;


@@ 274,10 280,10 @@ impl<'a> PasswordSafe<'a> {
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    pub fn get_slot_password(&self, slot: u8) -> Result<String, CommandError> {
        unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) }
    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)
    }



@@ 292,10 298,11 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// # 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)?;


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

    /// Erases the given slot.  Erasing clears the stored name, login and password (if the slot was


@@ 338,46 345,47 @@ impl<'a> PasswordSafe<'a> {
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::CommandError;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), CommandError> {
    /// let device = nitrokey::connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// # fn try_main() -> Result<(), Error> {
    /// 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."),
    ///     Err(err) => println!("Could not erase slot 0: {}", err),
    ///     Err(err) => eprintln!("Could not erase slot 0: {}", err),
    /// };
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
    pub fn erase_slot(&self, slot: u8) -> Result<(), CommandError> {
        unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) }
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    pub fn erase_slot(&mut self, slot: u8) -> Result<(), Error> {
        get_command_result(unsafe { nitrokey_sys::NK_erase_password_safe_slot(slot) })
    }
}

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(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
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(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
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(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError> {
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 src/util.rs => src/util.rs +39 -146
@@ 1,53 1,13 @@
use std::borrow;
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::{c_char, c_int};

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

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

/// Log level for libnitrokey.
///


@@ 70,126 30,59 @@ pub enum LogLevel {
    DebugL2,
}

pub fn owned_str_from_ptr(ptr: *const c_char) -> String {
    unsafe {
        return CStr::from_ptr(ptr).to_string_lossy().into_owned();
    }
pub fn owned_str_from_ptr(ptr: *const c_char) -> Result<String, Error> {
    unsafe { CStr::from_ptr(ptr) }
        .to_str()
        .map(String::from)
        .map_err(Error::from)
}

pub fn result_from_string(ptr: *const c_char) -> Result<String, CommandError> {
pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
    if ptr.is_null() {
        return Err(CommandError::Undefined);
        return Err(Error::UnexpectedError);
    }
    unsafe {
        let s = owned_str_from_ptr(ptr);
        free(ptr as *mut c_void);
        // An empty string can both indicate an error or be a valid return value.  In this case, we
        // have to check the last command status to decide what to return.
        if s.is_empty() {
            get_last_result().map(|_| s)
        } else {
            Ok(s)
        }
    let s = owned_str_from_ptr(ptr)?;
    unsafe { free(ptr as *mut c_void) };
    // An empty string can both indicate an error or be a valid return value.  In this case, we
    // have to check the last command status to decide what to return.
    if s.is_empty() {
        get_last_result().map(|_| s)
    } else {
        Ok(s)
    }
}

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

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

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

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

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

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

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

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

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

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