use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::crypto::{contains, intersects};
use super::document::DidResolver;
use super::envelope::{DidEnvelope, DidMessageBody};
use super::error::DidError;
use super::identifier::Did;
use super::keystore::DidKeyStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TypeDidMode {
Send,
RequestReply,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDidConversation {
pub conversation_id: String,
pub mode: TypeDidMode,
pub profile: String,
pub protocol: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}
impl TypeDidConversation {
pub fn new(
conversation_id: impl Into<String>,
mode: TypeDidMode,
profile: impl Into<String>,
protocol: impl Into<String>,
) -> Self {
Self {
conversation_id: conversation_id.into(),
mode,
profile: profile.into(),
protocol: protocol.into(),
expires_at: None,
}
}
pub fn with_expires_at(mut self, expires_at: u64) -> Self {
self.expires_at = Some(expires_at);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDidProfile {
pub id: String,
#[serde(default)]
pub did_methods: Vec<String>,
#[serde(default)]
pub signing: Vec<String>,
#[serde(default)]
pub key_agreement: Vec<String>,
#[serde(default)]
pub encryption: Vec<String>,
#[serde(default)]
pub transport_bindings: Vec<String>,
#[serde(default)]
pub modes: Vec<TypeDidMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_payload_bytes: Option<usize>,
#[serde(default)]
pub required_claims: Vec<String>,
#[serde(default)]
pub policy_actions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retention: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audit: Option<String>,
}
impl TypeDidProfile {
pub fn ed25519_x25519_chacha20() -> Self {
Self {
id: "typedid/v1/x25519-chacha20poly1305-ed25519".to_owned(),
did_methods: vec![
"did:web".to_owned(),
"did:key".to_owned(),
"did:indy".to_owned(),
],
signing: vec!["Ed25519".to_owned()],
key_agreement: vec!["X25519".to_owned()],
encryption: vec!["ChaCha20-Poly1305".to_owned()],
transport_bindings: vec![
"a2a".to_owned(),
"acp".to_owned(),
"band".to_owned(),
"https".to_owned(),
"websocket".to_owned(),
],
modes: vec![TypeDidMode::Send, TypeDidMode::RequestReply],
max_payload_bytes: Some(1024 * 1024),
required_claims: vec![
"org".to_owned(),
"agent_id".to_owned(),
"purpose".to_owned(),
],
policy_actions: vec![
"agent:message".to_owned(),
"agent:delegate".to_owned(),
"ai:infer".to_owned(),
],
retention: Some("sender-encrypted-payload-only".to_owned()),
audit: Some("envelope-metadata-and-policy-decision".to_owned()),
}
}
pub fn is_compatible_with(&self, remote: &Self, protocol: &str, mode: TypeDidMode) -> bool {
self.id == remote.id
&& contains(&self.transport_bindings, protocol)
&& contains(&remote.transport_bindings, protocol)
&& self.modes.contains(&mode)
&& remote.modes.contains(&mode)
&& intersects(&self.did_methods, &remote.did_methods)
&& intersects(&self.signing, &remote.signing)
&& intersects(&self.key_agreement, &remote.key_agreement)
&& intersects(&self.encryption, &remote.encryption)
}
pub fn negotiate<'a>(
local: &'a [Self],
remote: &[Self],
protocol: &str,
mode: TypeDidMode,
) -> Result<&'a Self, DidError> {
local
.iter()
.find(|candidate| {
remote
.iter()
.any(|other| candidate.is_compatible_with(other, protocol, mode))
})
.ok_or(DidError::NoCompatibleTypeDidProfile)
}
}
pub trait TypeDidProfileResolver: Send + Sync {
fn resolve_profiles(&self, target: &str) -> Result<Vec<TypeDidProfile>, DidError>;
}
#[derive(Debug, Default, Clone)]
pub struct StaticTypeDidProfileResolver {
profiles: HashMap<String, Vec<TypeDidProfile>>,
}
impl StaticTypeDidProfileResolver {
pub fn new() -> Self {
Self::default()
}
pub fn with_profiles(
mut self,
target: impl Into<String>,
profiles: Vec<TypeDidProfile>,
) -> Self {
self.profiles.insert(target.into(), profiles);
self
}
}
impl TypeDidProfileResolver for StaticTypeDidProfileResolver {
fn resolve_profiles(&self, target: &str) -> Result<Vec<TypeDidProfile>, DidError> {
self.profiles
.get(target)
.cloned()
.ok_or_else(|| DidError::Unresolved(target.to_owned()))
}
}
pub trait SecureEnvelopeAdapter {
fn protocol(&self) -> &str;
fn content_type(&self) -> &'static str {
"application/vnd.typedid.envelope+json"
}
fn wrap(
&self,
request: TypeDidWrapRequest<'_>,
resolver: &dyn DidResolver,
key_store: &dyn DidKeyStore,
) -> Result<DidEnvelope, DidError> {
let profile = TypeDidProfile::negotiate(
request.local_profiles,
request.remote_profiles,
self.protocol(),
request.mode,
)?;
if let Some(max) = profile.max_payload_bytes
&& request.payload.len() > max
{
return Err(DidError::PayloadTooLarge {
size: request.payload.len(),
max,
});
}
let conversation = TypeDidConversation::new(
request.conversation_id,
request.mode,
profile.id.clone(),
self.protocol(),
);
DidEnvelope::typedid(
request.id,
request.from,
request.to,
request.body,
conversation,
request.payload,
resolver,
key_store,
)
}
}
pub struct TypeDidWrapRequest<'a> {
pub id: String,
pub from: Did,
pub to: Did,
pub conversation_id: String,
pub mode: TypeDidMode,
pub body: DidMessageBody,
pub payload: &'a [u8],
pub local_profiles: &'a [TypeDidProfile],
pub remote_profiles: &'a [TypeDidProfile],
}
#[derive(Debug, Default, Clone, Copy)]
pub struct A2aTypeDidAdapter;
impl SecureEnvelopeAdapter for A2aTypeDidAdapter {
fn protocol(&self) -> &str {
"a2a"
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct AcpTypeDidAdapter;
impl SecureEnvelopeAdapter for AcpTypeDidAdapter {
fn protocol(&self) -> &str {
"acp"
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct BandSecureEnvelopeAdapter;
impl SecureEnvelopeAdapter for BandSecureEnvelopeAdapter {
fn protocol(&self) -> &str {
"band"
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HttpTypeDidAdapter;
impl SecureEnvelopeAdapter for HttpTypeDidAdapter {
fn protocol(&self) -> &str {
"https"
}
}