aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--TODO.md2
-rw-r--r--src/lib.rs68
-rw-r--r--tests/device.rs40
4 files changed, 105 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3798540..15c76b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ SPDX-License-Identifier: CC0-1.0
- Implement connection by path:
- Add the `Error::UnsupportedDeviceError` variant.
- Add the `DeviceInfo` struct.
+ - Add the `list_devices` function.
# v0.4.0 (2020-01-02)
- Remove the `test-pro` and `test-storage` features.
diff --git a/TODO.md b/TODO.md
index 8adea87..e3708f9 100644
--- a/TODO.md
+++ b/TODO.md
@@ -8,8 +8,6 @@ SPDX-License-Identifier: CC0-1.0
- `NK_get_SD_usage_data`
- `NK_get_progress_bar_value`
- `NK_get_status` -- waiting for [libnitrokey issue 166][]
- - `NK_list_devices`
- - `NK_free_device_info`
- `NK_connect_with_path`
- Clear passwords from memory.
- Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware
diff --git a/src/lib.rs b/src/lib.rs
index dad4a20..5600d24 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -109,8 +109,10 @@ mod otp;
mod pws;
mod util;
+use std::convert::TryInto as _;
use std::fmt;
use std::marker;
+use std::ptr::NonNull;
use std::sync;
use nitrokey_sys;
@@ -126,6 +128,8 @@ pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::util::LogLevel;
+use crate::util::get_last_result;
+
/// The default admin PIN for all Nitrokey devices.
pub const DEFAULT_ADMIN_PIN: &str = "12345678";
/// The default user PIN for all Nitrokey devices.
@@ -416,6 +420,70 @@ pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
}
}
+/// List all connected Nitrokey devices.
+///
+/// This functions returns a vector with [`DeviceInfo`][] structs that contain information about
+/// all connected Nitrokey devices. It will even list unsupported models, although you cannot
+/// connect to them.
+///
+/// # Errors
+///
+/// - [`NotConnected`][] if a Nitrokey device has been disconnected during enumeration
+/// - [`Utf8Error`][] if the USB path or the serial number returned by libnitrokey are invalid
+/// UTF-8 strings
+///
+/// # Example
+///
+/// ```
+/// let devices = nitrokey::list_devices()?;
+/// if devices.is_empty() {
+/// println!("No connected Nitrokey devices found.");
+/// } else {
+/// println!("model\tpath\tserial number");
+/// for device in devices {
+/// match device.model {
+/// Some(model) => print!("{}", model),
+/// None => print!("unsupported"),
+/// }
+/// print!("\t{}\t", device.path);
+/// match device.serial_number {
+/// Some(serial_number) => println!("{}", serial_number),
+/// None => println!("unknown"),
+/// }
+/// }
+/// }
+/// # Ok::<(), nitrokey::Error>(())
+/// ```
+///
+/// [`DeviceInfo`]: struct.DeviceInfo.html
+/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
+/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
+pub fn list_devices() -> Result<Vec<DeviceInfo>, Error> {
+ let ptr = NonNull::new(unsafe { nitrokey_sys::NK_list_devices() });
+ match ptr {
+ Some(mut ptr) => {
+ let mut vec: Vec<DeviceInfo> = Vec::new();
+ push_device_info(&mut vec, unsafe { ptr.as_ref() })?;
+ unsafe {
+ nitrokey_sys::NK_free_device_info(ptr.as_mut());
+ }
+ Ok(vec)
+ }
+ None => get_last_result().map(|_| Vec::new()),
+ }
+}
+
+fn push_device_info(
+ vec: &mut Vec<DeviceInfo>,
+ info: &nitrokey_sys::NK_device_info,
+) -> Result<(), Error> {
+ vec.push(info.try_into()?);
+ if let Some(ptr) = NonNull::new(info.next) {
+ push_device_info(vec, unsafe { ptr.as_ref() })?;
+ }
+ Ok(())
+}
+
/// 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/device.rs b/tests/device.rs
index e367558..5e60002 100644
--- a/tests/device.rs
+++ b/tests/device.rs
@@ -8,8 +8,8 @@ use std::process::Command;
use std::{thread, time};
use nitrokey::{
- Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, Error,
- GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode,
+ Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, DeviceInfo,
+ Error, GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode,
DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN,
};
use nitrokey_test::test as test_device;
@@ -32,6 +32,33 @@ fn count_nitrokey_block_devices() -> usize {
}
#[test_device]
+fn list_no_devices() {
+ let devices = nitrokey::list_devices();
+ assert_ok!(Vec::<DeviceInfo>::new(), devices);
+}
+
+#[test_device]
+fn list_devices(_device: DeviceWrapper) {
+ let devices = unwrap_ok!(nitrokey::list_devices());
+ for device in devices {
+ assert!(!device.path.is_empty());
+ if let Some(model) = device.model {
+ match model {
+ nitrokey::Model::Pro => {
+ assert!(device.serial_number.is_some());
+ let serial_number = device.serial_number.unwrap();
+ assert!(!serial_number.is_empty());
+ assert_valid_serial_number(&serial_number);
+ },
+ nitrokey::Model::Storage => {
+ assert_eq!(None, device.serial_number);
+ },
+ }
+ }
+ }
+}
+
+#[test_device]
fn connect_no_device() {
let mut manager = unwrap_ok!(nitrokey::take());
@@ -83,12 +110,17 @@ fn disconnect(device: DeviceWrapper) {
assert_empty_serial_number();
}
+fn assert_valid_serial_number(serial_number: &str) {
+ assert!(serial_number.is_ascii());
+ assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit()));
+}
+
#[test_device]
fn get_serial_number(device: DeviceWrapper) {
let serial_number = unwrap_ok!(device.get_serial_number());
- assert!(serial_number.is_ascii());
- assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit()));
+ assert_valid_serial_number(&serial_number);
}
+
#[test_device]
fn get_firmware_version(device: Pro) {
let version = unwrap_ok!(device.get_firmware_version());