use affinidi_messaging_didcomm::{DIDCommAgent, Message, UnpackResult};
use serde::{de::DeserializeOwned, Serialize};
use trust_tasks_rs::{Payload, TrustTask};
use crate::error::DidcommError;
use crate::handler::DidcommHandler;
pub const ENVELOPE_TYPE: &str = "https://trusttasks.org/binding/didcomm/0.1/envelope";
pub fn pack_trust_task<P>(
doc: &TrustTask<P>,
agent: &DIDCommAgent,
sender_did: &str,
recipient_did: &str,
) -> Result<String, DidcommError>
where
P: Payload + Serialize,
{
let body = serde_json::to_value(doc).map_err(DidcommError::SerialiseBody)?;
let thid = doc.thread_id.clone().unwrap_or_else(|| doc.id.clone());
let mut msg = Message::new(ENVELOPE_TYPE, body)
.from(sender_did.to_string())
.to(vec![recipient_did.to_string()])
.thid(thid);
if let Some(parent) = doc.parent_thread_id.clone() {
msg = msg.pthid(parent);
}
let wire = agent.pack_authcrypt(&msg, sender_did, recipient_did)?;
Ok(wire)
}
pub fn unpack_trust_task<P>(
wire: &str,
agent: &DIDCommAgent,
expected_sender_did: Option<&str>,
) -> Result<(TrustTask<P>, DidcommHandler), DidcommError>
where
P: Payload + DeserializeOwned,
{
let (message, peer_did, local_did) = match agent.unpack(wire, expected_sender_did)? {
UnpackResult::Encrypted {
message,
authenticated: true,
sender_kid: Some(sender_kid),
recipient_kid,
..
} => (
message,
did_from_kid(&sender_kid),
Some(did_from_kid(&recipient_kid).unwrap_or(recipient_kid)),
),
UnpackResult::Encrypted { .. } | UnpackResult::Plaintext(_) => {
return Err(DidcommError::UnauthenticatedSender);
}
UnpackResult::Signed {
message,
signer_kid: Some(signer_kid),
} => (message, did_from_kid(&signer_kid), None),
UnpackResult::Signed { .. } => {
return Err(DidcommError::UnauthenticatedSender);
}
_ => return Err(DidcommError::UnauthenticatedSender),
};
if message.typ != ENVELOPE_TYPE {
return Err(DidcommError::WrongEnvelopeType(message.typ.clone()));
}
let doc: TrustTask<P> =
serde_json::from_value(message.body).map_err(DidcommError::InvalidBody)?;
check_thread(
"thid",
"threadId",
message.thid.as_deref(),
doc.thread_id.as_deref(),
)?;
check_thread(
"pthid",
"parentThreadId",
message.pthid.as_deref(),
doc.parent_thread_id.as_deref(),
)?;
let handler = DidcommHandler::new(local_did, peer_did);
Ok((doc, handler))
}
fn check_thread(
header: &'static str,
member: &'static str,
transport: Option<&str>,
in_band: Option<&str>,
) -> Result<(), DidcommError> {
match (transport, in_band) {
(Some(t), Some(b)) if t != b => Err(DidcommError::ThreadMismatch {
header,
member,
transport: t.to_string(),
in_band: b.to_string(),
}),
_ => Ok(()),
}
}
fn did_from_kid(kid: &str) -> Option<String> {
kid.split_once('#').map(|(did, _)| did.to_string())
}
#[cfg(test)]
mod thread_tests {
use super::*;
#[test]
fn absent_on_either_side_is_not_a_mismatch() {
assert!(check_thread("thid", "threadId", None, Some("a")).is_ok());
assert!(check_thread("thid", "threadId", Some("a"), None).is_ok());
assert!(check_thread("thid", "threadId", None, None).is_ok());
}
#[test]
fn equal_values_pass() {
assert!(check_thread("thid", "threadId", Some("a"), Some("a")).is_ok());
}
#[test]
fn disagreement_is_a_malformed_request() {
let err = check_thread("pthid", "parentThreadId", Some("outer"), Some("other"))
.expect_err("both present and different must fail");
assert!(matches!(
err,
DidcommError::ThreadMismatch {
header: "pthid",
member: "parentThreadId",
..
}
));
match err.into_reject_reason() {
trust_tasks_rs::RejectReason::MalformedRequest { .. } => {}
other => panic!("expected malformedRequest, got {other:?}"),
}
}
}