use anyhow::{Context, Result};
use base64::{Engine as _, prelude::BASE64_STANDARD};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
use crate::progress::Progress;
use relay_lib::{
crypto::{SigningKey, StaticSecret, hex},
prelude::{Address, InboxId, KeyRecord},
};
#[derive(Clone, Serialize, Deserialize)]
pub struct Identity {
pub pub_record: KeyRecord,
pub inboxes: Vec<InboxId>,
#[serde(with = "hex::serde")]
pub signing_key: [u8; 32],
#[serde(with = "hex::serde")]
pub static_secret: [u8; 32],
}
impl Identity {
pub fn signing_key(&self) -> SigningKey {
SigningKey::from_bytes(&self.signing_key)
}
pub fn static_secret(&self) -> StaticSecret {
StaticSecret::from(self.static_secret)
}
pub fn print(&self, level: u32, progress: &mut Progress) {
progress.nested_section(level, &self.pub_record.id);
progress.nested(
level + 1,
&format!("Created {}", self.pub_record.created_at),
);
if let Some(expires_at) = self.pub_record.expires_at {
progress.nested(level + 1, &format!("Expires {}", expires_at));
} else {
progress.nested(level + 1, "Never Expires");
}
if !self.inboxes.is_empty() {
let mut inboxes = self.inboxes.clone();
inboxes.sort_by(|a, b| a.canonical().cmp(b.canonical()));
progress.nested_section(level + 1, "Inboxes");
for inbox in inboxes.iter() {
progress.nested(level + 2, &format!("{}", inbox));
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct StorageRoot {
pub identities: HashMap<Address, Identity>,
}
impl StorageRoot {
pub fn get_identity(&self, address: &Address) -> Option<&Identity> {
let mut address = address.clone();
address.inbox = None;
self.identities.get(&address)
}
pub fn export(&self, progress: &mut Progress, addresses: &[Address]) -> Result<String> {
progress.step("Validating addresses", "Validated addresses");
for address in addresses.iter() {
let mut addr = address.clone();
addr.inbox = None;
if !self.identities.contains_key(&addr) {
progress.abort(&format!(
"No identity found for address {}",
address.canonical()
));
}
if address.inbox().is_some() {
progress.abort("Please specify addresses without inbox IDs");
}
}
progress.step("Exporting identities", "Exported identities");
let identities = self
.identities
.iter()
.filter(|(addr, _)| addresses.contains(addr))
.map(|(_, identity)| identity.clone())
.collect::<Vec<_>>();
let json = serde_json::to_vec(&identities).context("Could not serialize identities")?;
Ok(BASE64_STANDARD.encode(json))
}
pub fn import(
&mut self,
progress: &mut Progress,
data: &str,
replace: bool,
) -> Result<Vec<Address>> {
progress.step("Decoding identity data", "Decoded identity data");
let json = BASE64_STANDARD
.decode(data)
.context("Could not decode base64 identity")?;
let identities: Vec<Identity> =
serde_json::from_slice(&json).context("Could not deserialize identities")?;
let identities = identities
.into_iter()
.map(|identity| {
let mut address = Address::parse(&identity.pub_record.id)
.expect("Invalid address in imported identity");
address.inbox = None;
(address, identity)
})
.collect::<HashMap<_, _>>();
progress.step(
"Checking for existing identities",
"Checked for existing identities",
);
for (addr, _) in identities.iter() {
if self.identities.contains_key(addr) {
if replace {
progress.nested(
1,
&format!("Replacing existing identity for address {}", addr),
);
} else {
progress.abort(&format!(
"Identity for address {} already exists. Use --replace to overwrite.",
addr
));
}
}
}
progress.step("Importing identities", "Imported identities");
let addresses: Vec<Address> = identities.keys().cloned().collect();
self.identities.extend(identities);
Ok(addresses)
}
}
#[derive(Clone)]
pub struct Storage {
pub root: StorageRoot,
}
impl Default for Storage {
fn default() -> Self {
Self {
root: StorageRoot {
identities: HashMap::new(),
},
}
}
}
impl Storage {
pub fn path() -> Result<PathBuf> {
let mut path = dirs::data_dir().context("Could not find data directory")?;
path.push("relay_cli");
std::fs::create_dir_all(&path).context("Could not create data directory")?;
path.push("storage.ron");
Ok(path)
}
pub fn init() -> Result<Self> {
if !Self::path()?.exists() {
Self::default().save()?;
}
let ron = std::fs::read_to_string(Self::path()?).context("Could not read storage file")?;
let root: StorageRoot =
ron::de::from_str(&ron).context("Could not deserialize storage file")?;
Ok(Self { root })
}
pub fn save(&self) -> Result<()> {
let ron = ron::ser::to_string_pretty(&self.root, ron::ser::PrettyConfig::default())
.context("Could not serialize storage")?;
std::fs::write(Self::path()?, ron).context("Could not write storage file")?;
Ok(())
}
pub fn delete(&self) -> Result<()> {
std::fs::remove_file(Self::path()?).context("Could not delete storage file")?;
let path = Self::path()?;
let parent = path.parent().context("Could not get parent directory")?;
std::fs::remove_dir_all(parent).context("Could not delete storage directory")?;
Ok(())
}
pub fn reset(&mut self) {
*self = Self::default();
}
}