diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | TODO.md | 2 | ||||
| -rw-r--r-- | src/lib.rs | 68 | ||||
| -rw-r--r-- | tests/device.rs | 40 | 
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. @@ -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 @@ -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()); | 
