diff options
author | Robin Krahl <robin.krahl@ireas.org> | 2019-01-27 15:43:32 +0000 |
---|---|---|
committer | Robin Krahl <robin.krahl@ireas.org> | 2019-07-08 21:27:11 +0000 |
commit | 588066f415e956fdcd2c6f6216c52b25911a3b1d (patch) | |
tree | b16014285812bf8286a6f76800c978f8da864486 | |
parent | a52676d9577f587e0f4d8e47ddc71ba34f0b31ca (diff) | |
download | nitrokey-rs-588066f415e956fdcd2c6f6216c52b25911a3b1d.tar.gz nitrokey-rs-588066f415e956fdcd2c6f6216c52b25911a3b1d.tar.bz2 |
Add Manager struct to manage Nitrokey connections
As part of the connection refactoring, we introduce the Manager struct
that deals with connection management. To make sure there can be only
once instance of the manager, we add a global static Mutex that holds
the single Manager instance. We use the struct to ensure that the user
can only connect to one device at a time.
This also changes the Error::PoisonError variant to store the
sync::PoisonError. This allows the user to call into_inner on the
PoisonError to retrieve the MutexGuard and to ignore the error (for
example useful during testing).
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/error.rs | 16 | ||||
-rw-r--r-- | src/lib.rs | 66 | ||||
-rw-r--r-- | tests/lib.rs | 16 |
5 files changed, 92 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 046b609..e67fd81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ SPDX-License-Identifier: MIT `nitrokey-test-state` dependency in version 0.1.0. - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. + - Add the `Manager` struct that manages connections to Nitrokey devices. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. @@ -17,6 +17,7 @@ license = "MIT" exclude = [".builds/*"] [dependencies] +lazy_static = "1.2.0" libc = "0.2" nitrokey-sys = "3.5" rand_core = {version = "0.3", default-features = false, features = ["std"] } diff --git a/src/error.rs b/src/error.rs index c6b19db..b84f5eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub enum Error { /// A library usage error. LibraryError(LibraryError), /// An error that occurred due to a poisoned lock. - PoisonError, + 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. @@ -70,14 +70,14 @@ impl From<str::Utf8Error> for Error { } } -impl<T> From<sync::PoisonError<T>> for Error { - fn from(_error: sync::PoisonError<T>) -> Self { - Error::PoisonError +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<T> From<sync::TryLockError<T>> for Error { - fn from(error: sync::TryLockError<T>) -> Self { +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, @@ -98,7 +98,7 @@ impl error::Error for Error { Error::CommunicationError(ref err) => Some(err), Error::ConcurrentAccessError => None, Error::LibraryError(ref err) => Some(err), - Error::PoisonError => None, + Error::PoisonError(ref err) => Some(err), Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::UnknownError(_) => None, @@ -114,7 +114,7 @@ impl fmt::Display for Error { 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::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), @@ -89,6 +89,9 @@ #![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)] +#[macro_use(lazy_static)] +extern crate lazy_static; + mod auth; mod config; mod device; @@ -98,6 +101,8 @@ mod pws; mod util; use std::fmt; +use std::marker; +use std::sync; use nitrokey_sys; @@ -117,6 +122,10 @@ pub const DEFAULT_ADMIN_PIN: &str = "12345678"; /// The default user PIN for all Nitrokey devices. pub const DEFAULT_USER_PIN: &str = "123456"; +lazy_static! { + static ref MANAGER: sync::Mutex<Manager> = sync::Mutex::new(Manager::new()); +} + /// A version of the libnitrokey library. /// /// Use the [`get_library_version`](fn.get_library_version.html) function to query the library @@ -147,6 +156,63 @@ impl fmt::Display for Version { } } +/// A manager for connections to Nitrokey devices. +/// +/// Currently, libnitrokey only provides access to one Nitrokey device at the same time. This +/// manager struct makes sure that `nitrokey-rs` does not try to connect to two devices at the same +/// time. +/// +/// To obtain an instance of this manager, use the [`take`][] function. +/// +/// [`take`]: fn.take.html +#[derive(Debug)] +pub struct Manager { + marker: marker::PhantomData<()>, +} + +impl Manager { + fn new() -> Self { + Manager { + marker: marker::PhantomData, + } + } +} + +/// Take an instance of the connection manager, blocking until an instance is available. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, this method blocks. If you want a non-blocking version, use [`take`][]. +/// +/// # Errors +/// +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take`]: fn.take.html +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take_blocking() -> Result<sync::MutexGuard<'static, Manager>, Error> { + MANAGER.lock().map_err(Into::into) +} + +/// Try to take an instance of the connection manager. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use +/// [`take_blocking`][]. +/// +/// # Errors +/// +/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take_blocking`]: fn.take_blocking.html +/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> { + MANAGER.try_lock().map_err(Into::into) +} + /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the /// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see /// [`set_log_level`][]). diff --git a/tests/lib.rs b/tests/lib.rs index 8ab75f6..25aae0f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -10,3 +10,19 @@ fn get_library_version() { assert!(version.git.is_empty() || version.git.starts_with("v")); assert!(version.major > 0); } + +#[test] +fn take_manager() { + assert!(nitrokey::take().is_ok()); + + let result = nitrokey::take(); + assert!(result.is_ok()); + let result2 = nitrokey::take(); + match result2 { + Ok(_) => panic!("Expected error, got Ok(_)!"), + Err(nitrokey::Error::ConcurrentAccessError) => {} + Err(err) => panic!("Expected ConcurrentAccessError, got {}", err), + } + drop(result); + assert!(nitrokey::take().is_ok()); +} |