use std::collections::HashMap;
use super::{VaultContentType, VaultError, VaultSecretKey, vault_content_type_from_app_name};
use crate::chunk::DataMapChunk;
use crate::client::Client;
use crate::client::GetError;
use crate::client::high_level::files::archive_private::PrivateArchiveDataMap;
use crate::client::high_level::files::archive_public::ArchiveAddress;
use crate::client::payment::PaymentOption;
use crate::data::DataAddress;
use crate::register::RegisterAddress;
use ant_evm::AttoTokens;
use ant_protocol::Bytes;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
pub static USER_DATA_VAULT_CONTENT_IDENTIFIER: LazyLock<VaultContentType> =
LazyLock::new(|| vault_content_type_from_app_name("UserData"));
pub type RegisterSecretKeyHex = String;
pub type ScratchpadSecretKeyHex = String;
pub type PointerSecretKeyHex = String;
#[derive(Debug, Clone, Serialize, Default, PartialEq, Eq, Deserialize)]
pub struct UserData {
pub file_archives: HashMap<ArchiveAddress, String>,
pub private_file_archives: HashMap<PrivateArchiveDataMap, String>,
pub register_addresses: HashMap<RegisterAddress, String>,
#[serde(default)]
pub register_key: Option<RegisterSecretKeyHex>,
#[serde(default)]
pub scratchpad_key: Option<ScratchpadSecretKeyHex>,
#[serde(default)]
pub pointer_key: Option<PointerSecretKeyHex>,
#[serde(default)]
pub public_files: HashMap<DataAddress, String>,
#[serde(default)]
pub private_files: HashMap<DataMapChunk, String>,
}
#[derive(Debug, thiserror::Error)]
pub enum UserDataVaultError {
#[error("Vault error: {0}")]
Vault(#[from] VaultError),
#[error("Unsupported vault content type: {0}")]
UnsupportedVaultContentType(VaultContentType),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Get error: {0}")]
GetError(#[from] GetError),
}
impl UserData {
pub fn new() -> Self {
Self::default()
}
pub fn add_register(&mut self, register: RegisterAddress, name: String) -> Option<String> {
self.register_addresses.insert(register, name)
}
pub fn add_file_archive(&mut self, archive: ArchiveAddress) -> Option<String> {
self.file_archives.insert(archive, "".into())
}
pub fn add_file_archive_with_name(
&mut self,
archive: ArchiveAddress,
name: String,
) -> Option<String> {
self.file_archives.insert(archive, name)
}
pub fn add_private_file_archive(&mut self, archive: PrivateArchiveDataMap) -> Option<String> {
self.private_file_archives.insert(archive, "".into())
}
pub fn add_private_file_archive_with_name(
&mut self,
archive: PrivateArchiveDataMap,
name: String,
) -> Option<String> {
self.private_file_archives.insert(archive, name)
}
pub fn remove_file_archive(&mut self, archive: ArchiveAddress) -> Option<String> {
self.file_archives.remove(&archive)
}
pub fn remove_private_file_archive(
&mut self,
archive: PrivateArchiveDataMap,
) -> Option<String> {
self.private_file_archives.remove(&archive)
}
pub fn to_bytes(&self) -> Result<Bytes, rmp_serde::encode::Error> {
let bytes = rmp_serde::to_vec(&self)?;
Ok(Bytes::from(bytes))
}
pub fn from_bytes(bytes: Bytes) -> Result<Self, rmp_serde::decode::Error> {
let vault_content = rmp_serde::from_slice(&bytes)?;
Ok(vault_content)
}
pub fn display_stats(&self) {
let file_archives_len = self.file_archives.len();
let private_file_archives_len = self.private_file_archives.len();
let public_files_len = self.public_files.len();
let private_files_len = self.private_files.len();
let registers_len = self.register_addresses.len();
let register_key = match self.register_key.is_some() {
true => "1",
false => "0",
};
let scratchpad_key = match self.scratchpad_key.is_some() {
true => "1",
false => "0",
};
let pointer_key = match self.pointer_key.is_some() {
true => "1",
false => "0",
};
println!("{file_archives_len} public file archive(s)");
println!("{private_file_archives_len} private file archive(s)");
println!("{public_files_len} public file(s)");
println!("{private_files_len} private file(s)");
println!("{registers_len} register(s)");
println!("{register_key} register key");
println!("{scratchpad_key} scratchpad key");
println!("{pointer_key} pointer key");
}
}
impl Client {
pub async fn vault_get_user_data(
&self,
secret_key: &VaultSecretKey,
) -> Result<UserData, UserDataVaultError> {
let (bytes, content_type) = self.vault_get(secret_key).await?;
if content_type != *USER_DATA_VAULT_CONTENT_IDENTIFIER {
return Err(UserDataVaultError::UnsupportedVaultContentType(
content_type,
));
}
let vault = UserData::from_bytes(bytes).map_err(|e| {
UserDataVaultError::Serialization(format!("Failed to deserialize vault content: {e}"))
})?;
Ok(vault)
}
pub async fn vault_put_user_data(
&self,
secret_key: &VaultSecretKey,
payment_option: PaymentOption,
user_data: UserData,
) -> Result<AttoTokens, UserDataVaultError> {
let bytes = user_data.to_bytes().map_err(|e| {
UserDataVaultError::Serialization(format!("Failed to serialize user data: {e}"))
})?;
let total_cost = self
.vault_put(
bytes,
payment_option,
secret_key,
*USER_DATA_VAULT_CONTENT_IDENTIFIER,
)
.await?;
Ok(total_cost)
}
#[deprecated(since = "0.2.0", note = "Use `vault_get_user_data` instead")]
pub async fn get_user_data_from_vault(
&self,
secret_key: &VaultSecretKey,
) -> Result<UserData, UserDataVaultError> {
self.vault_get_user_data(secret_key).await
}
#[deprecated(since = "0.2.0", note = "Use `vault_put_user_data` instead")]
pub async fn put_user_data_to_vault(
&self,
secret_key: &VaultSecretKey,
payment_option: PaymentOption,
user_data: UserData,
) -> Result<AttoTokens, UserDataVaultError> {
self.vault_put_user_data(secret_key, payment_option, user_data)
.await
}
}
#[cfg(test)]
mod tests {
use crate::XorName;
use bls::SecretKey;
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
struct UserDataV1 {
pub file_archives: HashMap<ArchiveAddress, String>,
pub private_file_archives: HashMap<PrivateArchiveDataMap, String>,
pub register_addresses: HashMap<RegisterAddress, String>,
}
#[test]
fn test_user_data_v1_deserialization() {
let v1_data = UserDataV1 {
file_archives: HashMap::from([(ArchiveAddress::new(XorName::random(&mut rand::thread_rng())), "test_archive".to_string())]),
private_file_archives: HashMap::from([(
PrivateArchiveDataMap::from_hex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").unwrap(),
"test_private".to_string(),
)]),
register_addresses: HashMap::from([(RegisterAddress::new(SecretKey::random().public_key()), "test_register".to_string())]),
};
let serialized = rmp_serde::to_vec(&v1_data).unwrap();
let deserialized: UserData = rmp_serde::from_slice(&serialized).unwrap();
assert_eq!(deserialized.file_archives, v1_data.file_archives);
assert_eq!(
deserialized.private_file_archives,
v1_data.private_file_archives
);
assert_eq!(deserialized.register_addresses, v1_data.register_addresses);
assert_eq!(deserialized.register_key, None);
assert_eq!(deserialized.scratchpad_key, None);
assert_eq!(deserialized.pointer_key, None);
assert_eq!(deserialized.public_files, HashMap::new());
assert_eq!(deserialized.private_files, HashMap::new());
let current_data = UserData {
file_archives: v1_data.file_archives.clone(),
private_file_archives: v1_data.private_file_archives.clone(),
register_addresses: v1_data.register_addresses.clone(),
register_key: Some("test_key".to_string()),
scratchpad_key: Some("test_scratchpad_key".to_string()),
pointer_key: Some("test_pointer_key".to_string()),
public_files: HashMap::new(),
private_files: HashMap::new(),
};
let serialized = rmp_serde::to_vec(¤t_data).unwrap();
let deserialized: UserData = rmp_serde::from_slice(&serialized).unwrap();
assert_eq!(deserialized, current_data);
}
}