diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/device/mod.rs | 105 | ||||
| -rw-r--r-- | src/lib.rs | 4 | 
2 files changed, 105 insertions, 4 deletions
| 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}; | 
