use crate::guardian::error::{GuardianError, Result};
use crate::p2p::network::client::IrohClient;
use iroh_blobs::Hash;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Manifest {
#[serde(rename = "type")]
pub get_type: String,
#[serde(rename = "params")]
pub params: CreateAccessControllerOptions,
}
#[derive(Debug, Clone)]
pub struct CreateAccessControllerOptions {
pub skip_manifest: bool,
pub address: Hash,
pub get_type: String,
pub name: String,
access: Arc<Mutex<HashMap<String, Vec<String>>>>,
}
impl Serialize for CreateAccessControllerOptions {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("CreateAccessControllerOptions", 5)?;
state.serialize_field("skip_manifest", &self.skip_manifest)?;
state.serialize_field("address", &hex::encode(self.address.as_bytes()))?; state.serialize_field("type", &self.get_type)?;
state.serialize_field("name", &self.name)?;
if let Ok(access_guard) = self.access.lock()
&& !access_guard.is_empty()
{
state.serialize_field("access", &*access_guard)?;
}
state.end()
}
}
impl<'de> Deserialize<'de> for CreateAccessControllerOptions {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{MapAccess, Visitor};
use std::fmt;
struct OptionsVisitor;
impl<'de> Visitor<'de> for OptionsVisitor {
type Value = CreateAccessControllerOptions;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct CreateAccessControllerOptions")
}
fn visit_map<V>(
self,
mut map: V,
) -> std::result::Result<CreateAccessControllerOptions, V::Error>
where
V: MapAccess<'de>,
{
let mut skip_manifest = false;
let mut address = Hash::from([0u8; 32]);
let mut type_field = String::new();
let mut name = String::new();
let mut access = HashMap::new();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"skip_manifest" => skip_manifest = map.next_value()?,
"address" => {
let hex_str: String = map.next_value()?;
address = hex::decode(&hex_str)
.ok()
.and_then(|bytes| {
if bytes.len() == 32 {
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Some(Hash::from(arr))
} else {
None
}
})
.unwrap_or_else(|| Hash::from([0u8; 32]));
}
"type" => type_field = map.next_value()?,
"name" => name = map.next_value()?,
"access" => access = map.next_value()?,
_ => {
let _ = map.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(CreateAccessControllerOptions {
skip_manifest,
address,
get_type: type_field,
name,
access: Arc::new(Mutex::new(access)),
})
}
}
deserializer.deserialize_struct(
"CreateAccessControllerOptions",
&["skip_manifest", "address", "type", "name", "access"],
OptionsVisitor,
)
}
}
impl Default for CreateAccessControllerOptions {
fn default() -> Self {
Self {
skip_manifest: false,
address: Hash::from([0u8; 32]),
get_type: String::new(),
name: String::new(),
access: Arc::new(Mutex::new(HashMap::new())),
}
}
}
pub trait ManifestParams: Send + Sync {
fn skip_manifest(&self) -> bool;
fn address(&self) -> Hash;
fn set_address(&mut self, addr: Hash);
fn get_type(&self) -> &str;
fn set_type(&mut self, t: String);
fn get_name(&self) -> &str;
fn set_name(&mut self, name: String);
fn set_access(&mut self, role: String, allowed: Vec<String>);
fn get_access(&self, role: &str) -> Option<Vec<String>>;
fn get_all_access(&self) -> HashMap<String, Vec<String>>;
fn as_any(&self) -> &dyn std::any::Any;
}
impl ManifestParams for CreateAccessControllerOptions {
fn skip_manifest(&self) -> bool {
self.skip_manifest
}
fn address(&self) -> Hash {
self.address
}
fn set_address(&mut self, addr: Hash) {
self.address = addr;
}
fn get_type(&self) -> &str {
&self.get_type
}
fn set_type(&mut self, t: String) {
self.get_type = t;
}
fn get_name(&self) -> &str {
&self.name
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_access(&mut self, role: String, allowed: Vec<String>) {
let mut guard = self.access.lock().expect("Failed to acquire lock");
guard.insert(role, allowed);
}
fn get_access(&self, role: &str) -> Option<Vec<String>> {
let guard = self.access.lock().expect("Failed to acquire lock");
guard.get(role).cloned()
}
fn get_all_access(&self) -> HashMap<String, Vec<String>> {
let guard = self.access.lock().expect("Failed to acquire lock");
guard.clone()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl CreateAccessControllerOptions {
pub fn new(address: Hash, skip_manifest: bool, manifest_type: String) -> Self {
Self {
address,
skip_manifest,
get_type: manifest_type,
..Default::default()
}
}
pub fn new_empty() -> Self {
Default::default()
}
pub fn new_simple(manifest_type: String, access: HashMap<String, Vec<String>>) -> Self {
Self {
skip_manifest: true,
get_type: manifest_type,
access: Arc::new(Mutex::new(access)),
..Default::default()
}
}
pub fn from_params(params: &CreateAccessControllerOptions) -> Self {
params.clone()
}
pub fn read_only_replication(writers: Vec<String>) -> Self {
let mut access = HashMap::new();
access.insert("write".to_string(), writers);
access.insert("read".to_string(), vec!["*".to_string()]);
Self::new_simple("simple".to_string(), access)
}
}
pub async fn create(
client: Arc<IrohClient>,
controller_type: String,
params: &CreateAccessControllerOptions,
) -> Result<Hash> {
if params.skip_manifest() {
return Ok(params.address());
}
if controller_type.is_empty() {
return Err(GuardianError::Store(
"Controller type cannot be empty".to_string(),
));
}
let manifest = Manifest {
get_type: controller_type,
params: CreateAccessControllerOptions {
skip_manifest: params.skip_manifest(),
address: params.address(),
get_type: params.get_type().to_string(),
name: params.get_name().to_string(),
access: params.access.clone(),
},
};
let cbor_data = serde_cbor::to_vec(&manifest)
.map_err(|e| GuardianError::Store(format!("Failed to serialize manifest: {}", e)))?;
if cbor_data.is_empty() {
return Err(GuardianError::Store(
"Serialized manifest is empty".to_string(),
));
}
let response = client
.add_bytes(cbor_data)
.await
.map_err(|e| GuardianError::Store(format!("Failed to store manifest in iroh: {}", e)))?;
if response.hash.is_empty() {
return Err(GuardianError::Store("iroh returned empty hash".to_string()));
}
let hash_bytes = hex::decode(&response.hash)
.map_err(|e| GuardianError::Store(format!("Invalid hex hash returned from iroh: {}", e)))?;
if hash_bytes.len() != 32 {
return Err(GuardianError::Store(format!(
"Invalid hash length: expected 32 bytes, got {}",
hash_bytes.len()
)));
}
let mut hash_array = [0u8; 32];
hash_array.copy_from_slice(&hash_bytes);
let hash = Hash::from(hash_array);
Ok(hash)
}
pub async fn resolve(
client: Arc<IrohClient>,
manifest_address: &str,
params: &CreateAccessControllerOptions,
) -> Result<Manifest> {
if params.skip_manifest() {
if params.get_type().is_empty() {
return Err(GuardianError::Store(
"Without a manifest, the access controller type is required".to_string(),
));
}
return Ok(Manifest {
get_type: params.get_type().to_string(),
params: params.clone(),
});
}
if manifest_address.is_empty() {
return Err(GuardianError::Store(
"Manifest address cannot be empty".to_string(),
));
}
let hash_str = if let Some(stripped) = manifest_address.strip_prefix("/iroh/") {
stripped
} else {
manifest_address
};
let data_bytes = client
.cat_bytes(hash_str)
.await
.map_err(|e| GuardianError::Store(format!("Failed to load manifest from Iroh: {}", e)))?;
if data_bytes.is_empty() {
return Err(GuardianError::Store(
"Retrieved manifest data is empty".to_string(),
));
}
let manifest: Manifest = serde_cbor::from_slice(&data_bytes)
.map_err(|e| GuardianError::Store(format!("Failed to deserialize manifest: {}", e)))?;
if manifest.get_type.is_empty() {
return Err(GuardianError::Store(
"Manifest type cannot be empty".to_string(),
));
}
Ok(manifest)
}
pub fn create_manifest_with_validation(
controller_type: String,
params: CreateAccessControllerOptions,
) -> Result<Manifest> {
if controller_type.is_empty() {
return Err(GuardianError::Store(
"Controller type cannot be empty".to_string(),
));
}
if controller_type.len() > 255 {
return Err(GuardianError::Store(
"Controller type too long (max 255 characters)".to_string(),
));
}
let valid_types = ["iroh", "GuardianDB", "simple"];
if !valid_types.contains(&controller_type.as_str()) {
return Err(GuardianError::Store(format!(
"Unknown controller type: {}",
controller_type
)));
}
Ok(Manifest {
get_type: controller_type,
params,
})
}