use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::domain::error::DppError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SealMode {
ProviderSeal,
OperatorSeal,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum SealFormat {
Jades,
Pades,
Cades,
Xades,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SealCredentialRef {
pub qtsp_id: String,
pub credential_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SealRequest {
pub payload_hash: String,
pub mode: SealMode,
pub key_ref: SealCredentialRef,
pub sig_format: SealFormat,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SealedEnvelope {
pub format: SealFormat,
pub seal_value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signing_cert_ref: Option<String>,
pub sealed_at: DateTime<Utc>,
pub placeholder: bool,
}
#[derive(Debug, Clone)]
pub struct SealCapabilities {
pub supported_formats: Vec<SealFormat>,
pub supported_modes: Vec<SealMode>,
}
#[derive(Debug, Clone)]
pub struct SealVerification {
pub valid: bool,
pub placeholder: bool,
}
#[async_trait]
pub trait SealPort: Send + Sync {
async fn seal(&self, req: SealRequest) -> Result<SealedEnvelope, DppError>;
async fn verify(&self, env: &SealedEnvelope) -> Result<SealVerification, DppError>;
fn capabilities(&self) -> SealCapabilities;
}
pub struct GhostSeal;
#[async_trait]
impl SealPort for GhostSeal {
async fn seal(&self, req: SealRequest) -> Result<SealedEnvelope, DppError> {
Ok(SealedEnvelope {
format: req.sig_format,
seal_value: format!(
"GHOST-SEAL-{}",
&req.payload_hash[..8.min(req.payload_hash.len())]
),
signing_cert_ref: None,
sealed_at: Utc::now(),
placeholder: true,
})
}
async fn verify(&self, env: &SealedEnvelope) -> Result<SealVerification, DppError> {
Ok(SealVerification {
valid: false,
placeholder: env.placeholder,
})
}
fn capabilities(&self) -> SealCapabilities {
SealCapabilities {
supported_formats: vec![SealFormat::Jades],
supported_modes: vec![SealMode::ProviderSeal, SealMode::OperatorSeal],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn ghost_seal_returns_placeholder() {
let ghost = GhostSeal;
let req = SealRequest {
payload_hash: "abc123def456".into(),
mode: SealMode::ProviderSeal,
key_ref: SealCredentialRef {
qtsp_id: "test-qtsp".into(),
credential_id: "cred-001".into(),
},
sig_format: SealFormat::Jades,
};
let env = ghost.seal(req).await.unwrap();
assert!(env.placeholder);
assert!(env.seal_value.starts_with("GHOST-SEAL-"));
assert_eq!(env.format, SealFormat::Jades);
}
#[tokio::test]
async fn ghost_verify_returns_invalid_placeholder() {
let ghost = GhostSeal;
let env = SealedEnvelope {
format: SealFormat::Jades,
seal_value: "GHOST-SEAL-abc123".into(),
signing_cert_ref: None,
sealed_at: Utc::now(),
placeholder: true,
};
let result = ghost.verify(&env).await.unwrap();
assert!(!result.valid);
assert!(result.placeholder);
}
#[tokio::test]
async fn ghost_capabilities_include_jades_and_both_modes() {
let caps = GhostSeal.capabilities();
assert!(caps.supported_formats.contains(&SealFormat::Jades));
assert!(caps.supported_modes.contains(&SealMode::ProviderSeal));
assert!(caps.supported_modes.contains(&SealMode::OperatorSeal));
}
#[test]
fn seal_format_serde_round_trips() {
for fmt in [
SealFormat::Jades,
SealFormat::Pades,
SealFormat::Cades,
SealFormat::Xades,
] {
let json = serde_json::to_string(&fmt).unwrap();
let back: SealFormat = serde_json::from_str(&json).unwrap();
assert_eq!(fmt, back);
}
}
#[test]
fn seal_mode_serde_round_trips() {
for mode in [SealMode::ProviderSeal, SealMode::OperatorSeal] {
let json = serde_json::to_string(&mode).unwrap();
let back: SealMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, back);
}
}
}