use serde::{Deserialize, Serialize};
use crate::said::{Protocol, compute_said_with_protocol, compute_section_said};
use crate::types::{Prefix, Said};
pub const ACDC_KERIPY_REVISION: &str = "keripy 1.3.4";
pub const ACDC_VERSION_PREFIX: &str = "ACDC10JSON";
pub const CAPABILITY_SCHEMA: &str = include_str!("acdc_capability_schema.json");
#[derive(Debug, thiserror::Error)]
pub enum AcdcError {
#[error("ACDC serialization failed: {0}")]
Serialization(#[from] serde_json::Error),
#[error("ACDC SAID computation failed: {0}")]
Said(#[from] crate::error::KeriTranslationError),
#[error("ACDC {layer} SAID mismatch: computed {computed}, found {found}")]
SaidMismatch {
layer: &'static str,
computed: String,
found: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Attributes {
pub d: Said,
pub i: Prefix,
pub dt: String,
#[serde(flatten)]
pub data: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Acdc {
pub v: String,
pub d: Said,
pub i: Prefix,
pub ri: Said,
pub s: Said,
pub a: Attributes,
}
const ACDC_VERSION_PLACEHOLDER: &str = "ACDC10JSON000000_";
impl Acdc {
pub fn new(
issuer: Prefix,
registry: Said,
schema: Said,
subject: Prefix,
dt: String,
data: serde_json::Map<String, serde_json::Value>,
) -> Self {
Self {
v: ACDC_VERSION_PLACEHOLDER.to_string(),
d: Said::default(),
i: issuer,
ri: registry,
s: schema,
a: Attributes {
d: Said::default(),
i: subject,
dt,
data,
},
}
}
pub fn saidify(mut self) -> Result<Self, AcdcError> {
let attr_value = serde_json::to_value(&self.a)?;
self.a.d = compute_section_said(&attr_value)?;
let body = serde_json::to_value(&self)?;
self.d = compute_said_with_protocol(&body, Protocol::Acdc)?;
self.v = self.recompute_version_string()?;
Ok(self)
}
fn recompute_version_string(&self) -> Result<String, AcdcError> {
let mut probe = self.clone();
probe.v = ACDC_VERSION_PLACEHOLDER.to_string();
let bytes = serde_json::to_vec(&probe)?;
Ok(format!("{ACDC_VERSION_PREFIX}{:06x}_", bytes.len()))
}
pub fn verify_said(&self) -> Result<(), AcdcError> {
let attr_value = serde_json::to_value(&self.a)?;
let attr_computed = compute_section_said(&attr_value)?;
if attr_computed != self.a.d {
return Err(AcdcError::SaidMismatch {
layer: "attributes",
computed: attr_computed.into_inner(),
found: self.a.d.as_str().to_string(),
});
}
let body = serde_json::to_value(self)?;
let computed = compute_said_with_protocol(&body, Protocol::Acdc)?;
if computed != self.d {
return Err(AcdcError::SaidMismatch {
layer: "credential",
computed: computed.into_inner(),
found: self.d.as_str().to_string(),
});
}
Ok(())
}
pub fn to_wire_bytes(&self) -> Result<Vec<u8>, AcdcError> {
Ok(serde_json::to_vec(self)?)
}
}
pub fn compute_capability_schema_said() -> Result<Said, AcdcError> {
let doc: serde_json::Value = serde_json::from_str(CAPABILITY_SCHEMA)?;
compute_schema_said(&doc)
}
pub fn compute_schema_said(schema: &serde_json::Value) -> Result<Said, AcdcError> {
let obj = schema
.as_object()
.ok_or(crate::error::KeriTranslationError::MissingField { field: "schema" })?;
let placeholder = serde_json::Value::String(crate::said::SAID_PLACEHOLDER.to_string());
let mut probe = serde_json::Map::new();
for (k, v) in obj {
if k == "$id" {
probe.insert("$id".to_string(), placeholder.clone());
} else {
probe.insert(k.clone(), v.clone());
}
}
if !probe.contains_key("$id") {
probe.insert("$id".to_string(), placeholder.clone());
}
let serialized = serde_json::to_vec(&serde_json::Value::Object(probe))
.map_err(crate::error::KeriTranslationError::SerializationFailed)?;
let hash = blake3::hash(&serialized);
#[allow(clippy::expect_used)] let said = crate::cesr_encode::encode_blake3_digest(hash.as_bytes())
.expect("32-byte Blake3 digest always encodes as a CESR Blake3_256 SAID");
Ok(Said::new_unchecked(said))
}