aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2019-01-27 15:43:32 +0000
committerRobin Krahl <robin.krahl@ireas.org>2019-07-08 21:27:11 +0000
commit588066f415e956fdcd2c6f6216c52b25911a3b1d (patch)
treeb16014285812bf8286a6f76800c978f8da864486
parenta52676d9577f587e0f4d8e47ddc71ba34f0b31ca (diff)
downloadnitrokey-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.md1
-rw-r--r--Cargo.toml1
-rw-r--r--src/error.rs16
-rw-r--r--src/lib.rs66
-rw-r--r--tests/lib.rs16
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.
diff --git a/Cargo.toml b/Cargo.toml
index d26bd88..7daadd1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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),
diff --git a/src/lib.rs b/src/lib.rs
index c35829c..573f45f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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());
+}