summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/device/mod.rs105
-rw-r--r--src/lib.rs4
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")
+ );
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0eb4f40..dad4a20 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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};