use std::sync::Arc;
use crate::storage::repository::TrustRecordRepository;
use affinidi_tdk::{
didcomm::Message,
messaging::{ATM, messages::compat::UnpackMetadata, profiles::ATMProfile},
};
use async_trait::async_trait;
use tracing::{debug, info, warn};
use crate::didcomm::{get_parent_thread_id, get_thread_id, listener::MessageHandler};
pub mod build;
pub mod problem_report;
pub mod trqp;
pub mod trust_tasks;
pub struct HandlerContext {
pub atm: Arc<ATM>,
pub profile: Arc<ATMProfile>,
pub sender_did: String,
pub authenticated_sender: Option<String>,
pub thid: Option<String>,
pub pthid: Option<String>,
}
#[async_trait]
pub trait ProtocolHandler: Send + Sync + 'static {
fn get_supported_inbound_message_types(&self) -> Vec<String>;
async fn handle(
&self,
ctx: &Arc<HandlerContext>,
message: Message,
meta: UnpackMetadata,
) -> Result<(), Box<dyn std::error::Error>>;
}
pub struct BaseHandler<R: ?Sized + TrustRecordRepository> {
#[allow(dead_code)]
repository: Arc<R>,
protocols_handlers: Vec<Arc<dyn ProtocolHandler>>,
}
#[async_trait]
impl<R: ?Sized + TrustRecordRepository + 'static> MessageHandler for BaseHandler<R> {
async fn handle(
&self,
atm: &Arc<ATM>,
profile: &Arc<ATMProfile>,
message: Message,
meta: UnpackMetadata,
) -> Result<(), Box<dyn std::error::Error>> {
let message_type = &message.typ;
let from = message.from.clone().unwrap_or("anon".into());
let authenticated_sender = (meta.authenticated && !meta.anonymous_sender)
.then(|| message.from.clone())
.flatten();
let thid = get_thread_id(&message).or_else(|| Some(message.id.clone()));
let pthid = get_parent_thread_id(&message);
let ctx = Arc::new(HandlerContext {
atm: atm.clone(),
profile: profile.clone(),
sender_did: from.clone(),
authenticated_sender,
thid,
pthid,
});
let ph = self.protocols_handlers.iter().find(|ph| {
ph.get_supported_inbound_message_types()
.contains(message_type)
});
if let Some(protocol_handler) = ph {
info!(
message_type = ?message_type,
from = ?from,
"[profile = {}] new message",
&profile.inner.alias
);
protocol_handler.handle(&ctx, message, meta).await?;
} else if is_mediator_transport_message(
profile.dids().ok().map(|(_, mediator)| mediator),
message_type,
&from,
meta.authenticated && !meta.anonymous_sender,
) {
debug!(
message_type = ?message_type,
"[profile = {}] unclaimed mediator transport message ignored",
&profile.inner.alias
);
} else {
warn!(
message_type = ?message.typ,
from = ?from,
"No handler found. Send problem report or ignore"
);
}
Ok(())
}
}
fn is_mediator_transport_message(
mediator_did: Option<&str>,
message_type: &str,
from: &str,
authenticated_sender: bool,
) -> bool {
const MESSAGE_PICKUP_PROTOCOL: &str = "https://didcomm.org/messagepickup/";
let Some(mediator_did) = mediator_did else {
return false;
};
authenticated_sender
&& from == mediator_did
&& message_type.starts_with(MESSAGE_PICKUP_PROTOCOL)
}
#[cfg(test)]
mod tests {
use super::is_mediator_transport_message;
const MEDIATOR: &str = "did:webvh:QmTS3a:webvh.storm.ws:mediator";
const STATUS: &str = "https://didcomm.org/messagepickup/3.0/status";
const AUTHENTICATED: bool = true;
const UNAUTHENTICATED: bool = false;
#[test]
fn pickup_frame_from_our_mediator_is_transport_chatter() {
assert!(is_mediator_transport_message(
Some(MEDIATOR),
STATUS,
MEDIATOR,
AUTHENTICATED
));
}
#[test]
fn pickup_frame_from_anyone_else_is_still_unhandled() {
assert!(!is_mediator_transport_message(
Some(MEDIATOR),
STATUS,
"did:webvh:QmXi1P:webvh.storm.ws:first-vtc",
AUTHENTICATED
));
}
#[test]
fn registry_message_from_the_mediator_is_still_unhandled() {
assert!(!is_mediator_transport_message(
Some(MEDIATOR),
"registry/record/query",
MEDIATOR,
AUTHENTICATED
));
}
#[test]
fn spoofed_mediator_did_on_an_unauthenticated_frame_is_still_unhandled() {
assert!(!is_mediator_transport_message(
Some(MEDIATOR),
STATUS,
MEDIATOR,
UNAUTHENTICATED
));
}
#[test]
fn an_authenticated_mediator_frame_is_unaffected_by_the_guard() {
assert!(is_mediator_transport_message(
Some(MEDIATOR),
"https://didcomm.org/messagepickup/3.0/delivery",
MEDIATOR,
AUTHENTICATED
));
}
#[test]
fn without_a_configured_mediator_nothing_is_transport_chatter() {
assert!(!is_mediator_transport_message(
None,
STATUS,
MEDIATOR,
AUTHENTICATED
));
}
}