use std::fmt::{Debug, Display};
use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde::Serialize;
use wildland_crypto::identity::SigningKeypair;
use super::api::LocalSecureStorage;
use super::result::LssResult;
use crate::{ForestIdentity, ForestRetrievalError, LssError, WildlandIdentity, DEFAULT_FOREST_KEY};
#[derive(Clone)]
pub struct LssService {
lss: Arc<dyn LocalSecureStorage>,
}
const THIS_DEVICE_KEYPAIR_KEY: &str = "wildland.device.keypair";
const THIS_DEVICE_NAME_KEY: &str = "wildland.device.name";
impl LssService {
pub fn new(lss: Box<dyn LocalSecureStorage>) -> Self {
tracing::debug!("created new instance");
Self { lss: lss.into() }
}
pub fn save_identity(&self, wildland_identity: &WildlandIdentity) -> LssResult<bool> {
let key = match wildland_identity {
WildlandIdentity::Forest(_, _) => wildland_identity.to_string(),
WildlandIdentity::Device(device_name, _) => {
self.serialize_and_save(THIS_DEVICE_NAME_KEY, &device_name)?;
THIS_DEVICE_KEYPAIR_KEY.to_owned()
}
};
self.serialize_and_save(key, &wildland_identity.get_keypair())
}
pub fn get_default_forest_identity(
&self,
) -> Result<Option<ForestIdentity>, ForestRetrievalError> {
tracing::trace!("Getting default forest identity.");
let optional_default_forest_identity = self.get_default_forest_keypair()?;
optional_default_forest_identity.map_or(Ok(None), |default_forest_value| {
Ok(Some(ForestIdentity::new(0, default_forest_value)))
})
}
pub fn get_this_device_identity(&self) -> LssResult<Option<WildlandIdentity>> {
tracing::trace!("Getting this device identity.");
let optional_this_device_identity = self.get_this_device_keypair()?;
optional_this_device_identity.map_or(Ok(None), |this_device_identity| {
let device_name = self.get_this_device_name()?.ok_or_else(|| {
LssError::Error("Could not retrieve device name from LSS".to_owned())
})?;
Ok(Some(WildlandIdentity::Device(
device_name,
this_device_identity,
)))
})
}
fn get_this_device_name(&self) -> LssResult<Option<String>> {
self.get_parsed(THIS_DEVICE_NAME_KEY)
}
fn get_this_device_keypair(&self) -> LssResult<Option<SigningKeypair>> {
self.get_parsed(THIS_DEVICE_KEYPAIR_KEY)
}
fn get_default_forest_keypair(&self) -> LssResult<Option<SigningKeypair>> {
self.get_parsed(DEFAULT_FOREST_KEY)
}
fn serialize_and_save(
&self,
key: impl Display + Debug,
obj: &impl Serialize,
) -> LssResult<bool> {
self.lss
.insert(
key.to_string(),
serde_json::to_string(obj)
.map_err(|e| LssError::Error(format!("Could not serialize object: {e}")))?,
)
.map(|bytes| bytes.is_some())
}
fn get_parsed<T: DeserializeOwned>(&self, key: impl Display + Debug) -> LssResult<Option<T>> {
self.lss.get(key.to_string()).and_then(|optional_bytes| {
optional_bytes.map_or(Ok(None), |input| {
serde_json::from_str(&input)
.map_err(|e| LssError::Error(format!("Could not parse LSS entry: {e}")))
})
})
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::RwLock;
use rstest::{fixture, rstest};
use wildland_crypto::identity::SigningKeypair;
use crate::lss::service::{THIS_DEVICE_KEYPAIR_KEY, THIS_DEVICE_NAME_KEY};
use crate::test_utilities::create_signing_keypair;
use crate::{
ForestIdentity,
LocalSecureStorage,
LssResult,
LssService,
WildlandIdentity,
DEFAULT_FOREST_KEY,
};
#[fixture]
fn lss_stub() -> Box<dyn LocalSecureStorage> {
#[derive(Default)]
struct LssStub {
storage: RwLock<HashMap<String, String>>,
}
impl LocalSecureStorage for LssStub {
fn insert(&self, key: String, value: String) -> LssResult<Option<String>> {
Ok(self.storage.write().unwrap().insert(key, value))
}
fn get(&self, key: String) -> LssResult<Option<String>> {
Ok(self.storage.read().unwrap().get(&key).cloned())
}
fn contains_key(&self, key: String) -> LssResult<bool> {
Ok(self.storage.read().unwrap().contains_key(&key))
}
fn keys(&self) -> LssResult<Vec<String>> {
Ok(self.storage.read().unwrap().keys().cloned().collect())
}
fn keys_starting_with(&self, prefix: String) -> LssResult<Vec<String>> {
Ok(self
.storage
.read()
.unwrap()
.keys()
.filter(|key| key.starts_with(&prefix))
.cloned()
.collect())
}
fn remove(&self, key: String) -> LssResult<Option<String>> {
Ok(self.storage.write().unwrap().remove(&key))
}
fn len(&self) -> LssResult<usize> {
Ok(self.storage.read().unwrap().len())
}
fn is_empty(&self) -> LssResult<bool> {
Ok(self.storage.read().unwrap().is_empty())
}
}
Box::<LssStub>::default()
}
#[rstest]
fn test_save_forest_identity(lss_stub: Box<dyn LocalSecureStorage>) {
let service = LssService::new(lss_stub);
let keypair = create_signing_keypair();
let forest_identity = WildlandIdentity::Forest(5, SigningKeypair::from(&keypair));
service.save_identity(&forest_identity).unwrap();
let expected_key = "wildland.forest.5".to_string();
let deserialized_keypair: SigningKeypair =
service.get_parsed(expected_key).unwrap().unwrap();
assert_eq!(deserialized_keypair, keypair);
}
#[rstest]
fn test_save_device_identity(lss_stub: Box<dyn LocalSecureStorage>) {
let service = LssService::new(lss_stub);
let device_name = "some device".to_owned();
let keypair = create_signing_keypair();
let device_identity =
WildlandIdentity::Device(device_name.clone(), SigningKeypair::from(&keypair));
service.save_identity(&device_identity).unwrap();
let deserialized_keypair: SigningKeypair = service
.get_parsed(THIS_DEVICE_KEYPAIR_KEY)
.unwrap()
.unwrap();
assert_eq!(deserialized_keypair, keypair);
let deserialized_name: String = service.get_parsed(THIS_DEVICE_NAME_KEY).unwrap().unwrap();
assert_eq!(deserialized_name, device_name);
}
#[rstest]
fn get_default_forest_should_return_none(lss_stub: Box<dyn LocalSecureStorage>) {
let service = LssService::new(lss_stub);
let default_forest = service.get_default_forest_identity().unwrap();
assert!(default_forest.is_none())
}
#[rstest]
fn get_default_forest_should_return_identity(lss_stub: Box<dyn LocalSecureStorage>) {
let keypair = create_signing_keypair();
lss_stub
.insert(
DEFAULT_FOREST_KEY.to_owned(),
serde_json::to_string(&keypair).unwrap(),
)
.unwrap();
let service = LssService::new(lss_stub);
let default_forest = service.get_default_forest_identity().unwrap();
let expecte_forest_identity = ForestIdentity::new(0, SigningKeypair::from(&keypair));
assert_eq!(default_forest.unwrap(), expecte_forest_identity)
}
#[rstest]
fn test_get_this_device_identity_should_return_none(lss_stub: Box<dyn LocalSecureStorage>) {
let service = LssService::new(lss_stub);
let device_identity = service.get_this_device_identity().unwrap();
assert!(device_identity.is_none())
}
#[rstest]
fn test_get_this_device_identity_should_return_identity(lss_stub: Box<dyn LocalSecureStorage>) {
let device_name = "some device".to_owned();
let keypair = create_signing_keypair();
lss_stub
.insert(
THIS_DEVICE_NAME_KEY.to_owned(),
serde_json::to_string(&device_name).unwrap(),
)
.unwrap();
lss_stub
.insert(
THIS_DEVICE_KEYPAIR_KEY.to_owned(),
serde_json::to_string(&keypair).unwrap(),
)
.unwrap();
let service = LssService::new(lss_stub);
let device_identity = service.get_this_device_identity().unwrap().unwrap();
let expected_device_identity =
WildlandIdentity::Device(device_name, SigningKeypair::from(&keypair));
assert_eq!(device_identity, expected_device_identity);
}
}