use std::convert::TryFrom as _;
use std::fmt;
use std::ops;
use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status};
use crate::error::{CommandError, Error};
use crate::otp::GenerateOtp;
use crate::util::{get_command_result, get_cstring, get_last_error, get_struct};
#[derive(Debug)]
pub struct Storage<'a> {
manager: Option<&'a mut crate::Manager>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VolumeMode {
ReadOnly,
ReadWrite,
}
impl fmt::Display for VolumeMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
VolumeMode::ReadOnly => "read-only",
VolumeMode::ReadWrite => "read-write",
})
}
}
#[derive(Debug)]
pub struct VolumeStatus {
pub read_only: bool,
pub active: bool,
}
#[derive(Debug)]
pub struct SdCardData {
pub serial_number: u32,
pub size: u8,
pub manufacturing_year: u8,
pub manufacturing_month: u8,
pub oem: u16,
pub manufacturer: u8,
}
#[derive(Debug)]
pub struct StorageProductionInfo {
pub firmware_version: FirmwareVersion,
pub firmware_version_internal: u8,
pub serial_number_cpu: u32,
pub sd_card: SdCardData,
}
#[derive(Debug)]
pub struct StorageStatus {
pub unencrypted_volume: VolumeStatus,
pub encrypted_volume: VolumeStatus,
pub hidden_volume: VolumeStatus,
pub firmware_version: FirmwareVersion,
pub firmware_locked: bool,
pub serial_number_sd_card: u32,
pub serial_number_smart_card: u32,
pub user_retry_count: u8,
pub admin_retry_count: u8,
pub new_sd_card_found: bool,
pub filled_with_random: bool,
pub stick_initialized: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OperationStatus {
Ongoing(u8),
Idle,
}
impl<'a> Storage<'a> {
pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {
Storage {
manager: Some(manager),
}
}
pub fn change_update_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
let current_string = get_cstring(current)?;
let new_string = get_cstring(new)?;
get_command_result(unsafe {
nitrokey_sys::NK_change_update_password(current_string.as_ptr(), new_string.as_ptr())
})
}
pub fn enable_firmware_update(&mut self, update_pin: &str) -> Result<(), Error> {
let update_pin_string = get_cstring(update_pin)?;
get_command_result(unsafe {
nitrokey_sys::NK_enable_firmware_update(update_pin_string.as_ptr())
})
}
pub fn enable_encrypted_volume(&mut self, user_pin: &str) -> Result<(), Error> {
let user_pin = get_cstring(user_pin)?;
get_command_result(unsafe { nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr()) })
}
pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> {
get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() })
}
pub fn enable_hidden_volume(&mut self, volume_password: &str) -> Result<(), Error> {
let volume_password = get_cstring(volume_password)?;
get_command_result(unsafe {
nitrokey_sys::NK_unlock_hidden_volume(volume_password.as_ptr())
})
}
pub fn disable_hidden_volume(&mut self) -> Result<(), Error> {
get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() })
}
pub fn create_hidden_volume(
&mut self,
slot: u8,
start: u8,
end: u8,
password: &str,
) -> Result<(), Error> {
let password = get_cstring(password)?;
get_command_result(unsafe {
nitrokey_sys::NK_create_hidden_volume(slot, start, end, password.as_ptr())
})
}
pub fn set_unencrypted_volume_mode(
&mut self,
admin_pin: &str,
mode: VolumeMode,
) -> Result<(), Error> {
let admin_pin = get_cstring(admin_pin)?;
let result = match mode {
VolumeMode::ReadOnly => unsafe {
nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr())
},
VolumeMode::ReadWrite => unsafe {
nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr())
},
};
get_command_result(result)
}
pub fn set_encrypted_volume_mode(
&mut self,
admin_pin: &str,
mode: VolumeMode,
) -> Result<(), Error> {
let admin_pin = get_cstring(admin_pin)?;
let result = match mode {
VolumeMode::ReadOnly => unsafe {
nitrokey_sys::NK_set_encrypted_read_only(admin_pin.as_ptr())
},
VolumeMode::ReadWrite => unsafe {
nitrokey_sys::NK_set_encrypted_read_write(admin_pin.as_ptr())
},
};
get_command_result(result)
}
pub fn get_storage_status(&self) -> Result<StorageStatus, Error> {
get_struct(|out| unsafe { nitrokey_sys::NK_get_status_storage(out) })
}
pub fn get_production_info(&self) -> Result<StorageProductionInfo, Error> {
get_struct(|out| unsafe { nitrokey_sys::NK_get_storage_production_info(out) })
}
pub fn clear_new_sd_card_warning(&mut self, admin_pin: &str) -> Result<(), Error> {
let admin_pin = get_cstring(admin_pin)?;
get_command_result(unsafe {
nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())
})
}
pub fn get_sd_card_usage(&self) -> Result<ops::Range<u8>, Error> {
let mut usage_data = nitrokey_sys::NK_SD_usage_data::default();
let result = unsafe { nitrokey_sys::NK_get_SD_usage_data(&mut usage_data) };
match get_command_result(result) {
Ok(_) => {
if usage_data.write_level_min > usage_data.write_level_max
|| usage_data.write_level_max > 100
{
Err(Error::UnexpectedError("Invalid write levels".to_owned()))
} else {
Ok(ops::Range {
start: usage_data.write_level_min,
end: usage_data.write_level_max,
})
}
}
Err(err) => Err(err),
}
}
pub fn wink(&mut self) -> Result<(), Error> {
get_command_result(unsafe { nitrokey_sys::NK_wink() })
}
pub fn get_operation_status(&self) -> Result<OperationStatus, Error> {
let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() };
match status {
0..=100 => u8::try_from(status)
.map(OperationStatus::Ongoing)
.map_err(|_| {
Error::UnexpectedError("Cannot create u8 from operation status".to_owned())
}),
-1 => Ok(OperationStatus::Idle),
-2 => Err(get_last_error()),
_ => Err(Error::UnexpectedError(
"Invalid operation status".to_owned(),
)),
}
}
pub fn fill_sd_card(&mut self, admin_pin: &str) -> Result<(), Error> {
let admin_pin_string = get_cstring(admin_pin)?;
get_command_result(unsafe {
nitrokey_sys::NK_fill_SD_card_with_random_data(admin_pin_string.as_ptr())
})
.or_else(|err| match err {
Error::CommandError(CommandError::WrongCrc) => Ok(()),
err => Err(err),
})
}
pub fn export_firmware(&mut self, admin_pin: &str) -> Result<(), Error> {
let admin_pin_string = get_cstring(admin_pin)?;
get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) })
}
}
impl<'a> Drop for Storage<'a> {
fn drop(&mut self) {
unsafe {
nitrokey_sys::NK_logout();
}
}
}
impl<'a> Device<'a> for Storage<'a> {
fn into_manager(mut self) -> &'a mut crate::Manager {
self.manager.take().unwrap()
}
fn get_model(&self) -> Model {
Model::Storage
}
fn get_status(&self) -> Result<Status, Error> {
let mut status: Status = get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })?;
let storage_status = self.get_storage_status()?;
status.firmware_version = storage_status.firmware_version;
status.serial_number = SerialNumber::new(storage_status.serial_number_smart_card);
Ok(status)
}
}
impl<'a> GenerateOtp for Storage<'a> {}
impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {
fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {
Self {
firmware_version: FirmwareVersion {
major: data.FirmwareVersion_au8[0],
minor: data.FirmwareVersion_au8[1],
},
firmware_version_internal: data.FirmwareVersionInternal_u8,
serial_number_cpu: data.CPU_CardID_u32,
sd_card: SdCardData {
serial_number: data.SD_CardID_u32,
size: data.SD_Card_Size_u8,
manufacturing_year: data.SD_Card_ManufacturingYear_u8,
manufacturing_month: data.SD_Card_ManufacturingMonth_u8,
oem: data.SD_Card_OEM_u16,
manufacturer: data.SD_Card_Manufacturer_u8,
},
}
}
}
impl From<nitrokey_sys::NK_storage_status> for StorageStatus {
fn from(status: nitrokey_sys::NK_storage_status) -> Self {
StorageStatus {
unencrypted_volume: VolumeStatus {
read_only: status.unencrypted_volume_read_only,
active: status.unencrypted_volume_active,
},
encrypted_volume: VolumeStatus {
read_only: status.encrypted_volume_read_only,
active: status.encrypted_volume_active,
},
hidden_volume: VolumeStatus {
read_only: status.hidden_volume_read_only,
active: status.hidden_volume_active,
},
firmware_version: FirmwareVersion {
major: status.firmware_version_major,
minor: status.firmware_version_minor,
},
firmware_locked: status.firmware_locked,
serial_number_sd_card: status.serial_number_sd_card,
serial_number_smart_card: status.serial_number_smart_card,
user_retry_count: status.user_retry_count,
admin_retry_count: status.admin_retry_count,
new_sd_card_found: status.new_sd_card_found,
filled_with_random: status.filled_with_random,
stick_initialized: status.stick_initialized,
}
}
}