diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | src/device/mod.rs | 105 | ||||
-rw-r--r-- | src/lib.rs | 4 |
3 files changed, 106 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bf782..3798540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ SPDX-License-Identifier: CC0-1.0 - `NK_send_startup` - Implement connection by path: - Add the `Error::UnsupportedDeviceError` variant. + - Add the `DeviceInfo` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/src/device/mod.rs b/src/device/mod.rs index a3a0255..e015886 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -5,7 +5,9 @@ mod pro; mod storage; mod wrapper; -use std::convert::TryFrom; +use std::cmp; +use std::convert::{TryFrom, TryInto}; +use std::ffi; use std::fmt; use libc; @@ -17,7 +19,8 @@ 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, + get_command_result, get_cstring, get_last_error, owned_str_from_ptr, result_from_string, + result_or_error, }; pub use pro::Pro; @@ -68,6 +71,73 @@ impl TryFrom<nitrokey_sys::NK_device_model> for Model { } } +/// Connection information for a Nitrokey device. +#[derive(Clone, Debug, PartialEq)] +pub struct DeviceInfo { + /// The model of the Nitrokey device, or `None` if the model is not supported by this crate. + pub model: Option<Model>, + /// The USB device path. + pub path: String, + /// The serial number as a 8-character hex string, or `None` if the device does not expose its + /// serial number. + pub serial_number: Option<String>, +} + +impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo { + type Error = Error; + + fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result<DeviceInfo, Error> { + let model_result = device_info.model.try_into(); + let model_option = model_result.map(Some).or_else(|err| match err { + Error::UnsupportedModelError => Ok(None), + _ => Err(err), + })?; + let serial_number = unsafe { ffi::CStr::from_ptr(device_info.serial_number) } + .to_str() + .map_err(Error::from)?; + Ok(DeviceInfo { + model: model_option, + path: owned_str_from_ptr(device_info.path)?, + serial_number: get_hidapi_serial_number(serial_number), + }) + } +} + +impl fmt::Display for DeviceInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.model { + Some(model) => write!(f, "Nitrokey {}", model)?, + None => write!(f, "Unsupported Nitrokey model")?, + } + write!(f, " at {} with ", self.path)?; + match &self.serial_number { + Some(ref serial_number) => write!(f, "serial no. {}", serial_number), + None => write!(f, "an unknown serial number"), + } + } +} + +/// Parses a serial number returned by hidapi and transforms it to the Nitrokey format. +/// +/// If the serial number is all zero, this function returns `None`. Otherwise, all leading zeros +/// (except the last eight characters) are stripped and `Some(_)` is returned. If the serial +/// number has less than eight characters, leading zeros are added. +fn get_hidapi_serial_number(serial_number: &str) -> Option<String> { + let mut iter = serial_number.char_indices().skip_while(|(_, c)| *c == '0'); + let first_non_null = iter.next(); + if let Some((i, _)) = first_non_null { + // keep at least the last 8 characters + let len = serial_number.len(); + let cut = cmp::min(len.saturating_sub(8), i); + let (_, suffix) = serial_number.split_at(cut); + // if necessary, add leading zeros to reach 8 characters + let fill = 8usize.saturating_sub(len); + Some("0".repeat(fill) + suffix) + } else { + None + } +} + /// A firmware version for a Nitrokey device. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FirmwareVersion { @@ -476,3 +546,34 @@ pub(crate) fn get_connected_device( pub(crate) fn connect_enum(model: Model) -> bool { unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 } } + +#[cfg(test)] +mod tests { + use super::get_hidapi_serial_number; + + #[test] + fn hidapi_serial_number() { + assert_eq!(None, get_hidapi_serial_number("")); + assert_eq!(None, get_hidapi_serial_number("00000000000000000")); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("1234") + ); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("00001234") + ); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("000000001234") + ); + assert_eq!( + Some("100000001234".to_string()), + get_hidapi_serial_number("100000001234") + ); + assert_eq!( + Some("10000001234".to_string()), + get_hidapi_serial_number("010000001234") + ); + } +} @@ -118,8 +118,8 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, - VolumeMode, VolumeStatus, + Device, DeviceInfo, 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}; |