use serde_json::Value;
use trust_tasks_rs::specs::trust_task_discovery::v0_3 as wire;
use trust_tasks_rs::validate::ValidatedPayload;
use trust_tasks_rs::{RejectReason, TrustTask};
use super::helpers::{TrustTaskOutcome, parse_payload, reject_with, success_response};
use super::{DISPATCHED_URIS, JoinAuthCtx, caller_is_identified};
pub(crate) const DISCOVERY_V0_3_TYPE: &str = vti_common::trust_task::discovery::DISCOVERY_V0_3;
pub(super) fn served_uris() -> Vec<&'static str> {
DISPATCHED_URIS
.iter()
.copied()
.chain(crate::rooms::handlers::served_uris())
.chain(crate::git_ns::tasks::served_uris())
.collect()
}
pub(super) fn handle(ctx: &JoinAuthCtx, doc: TrustTask<Value>) -> TrustTaskOutcome {
if !caller_is_identified(ctx) {
return reject_with(
&doc,
RejectReason::PermissionDenied {
reason: "this community lists the Trust Tasks it serves to callers it can \
identify — ask over DIDComm or TSP, or with a signed Trust Task document"
.to_string(),
},
);
}
if let Err(e) = wire::Payload::validate_value(&doc.payload) {
return reject_with(
&doc,
RejectReason::MalformedRequest {
reason: format!("payload: {e}"),
},
);
}
let query: wire::Payload = match parse_payload(&doc) {
Ok(q) => q,
Err(reject) => return reject,
};
success_response(
&doc,
vti_common::trust_task::discovery::respond_v0_3(served_uris(), &query),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::join::JoinTransport;
use vti_common::trust_task::acceptance::AcceptanceWindow;
fn query(payload: Value) -> TrustTask<Value> {
let mut doc = TrustTask::new(
"urn:uuid:00000000-0000-4000-8000-000000000001".to_string(),
DISCOVERY_V0_3_TYPE.parse().unwrap(),
payload,
);
doc.issuer = Some("did:example:member".into());
doc.recipient = Some("did:example:vtc".into());
doc
}
fn identified() -> JoinAuthCtx {
JoinAuthCtx {
transport: JoinTransport::Rest,
sender_did: None,
verified_signer: Some("did:example:member".into()),
}
}
fn answer(outcome: TrustTaskOutcome) -> Value {
serde_json::from_slice(&outcome.body).expect("a JSON document")
}
#[test]
fn vti_trn_047_the_advertised_window_is_the_applied_window() {
let body = answer(handle(&identified(), query(serde_json::json!({}))));
assert_eq!(
body["type"],
format!("{DISCOVERY_V0_3_TYPE}#response"),
"{body}"
);
let window: wire::AcceptanceWindow =
serde_json::from_value(body["payload"]["acceptanceWindow"].clone())
.unwrap_or_else(|e| panic!("VTI-TRN-047: a response-level window ({e}): {body}"));
let advertised = AcceptanceWindow::from_advertised(&window);
let applied = super::super::freshness_policy();
assert_eq!(Some(advertised.max_age), applied.max_age);
assert_eq!(advertised.clock_skew, applied.skew);
}
#[test]
fn vti_trn_047_a_document_at_the_advertised_edge_is_accepted_and_one_past_it_refused() {
let body = answer(handle(&identified(), query(serde_json::json!({}))));
let window: wire::AcceptanceWindow =
serde_json::from_value(body["payload"]["acceptanceWindow"].clone()).expect("window");
let w = AcceptanceWindow::from_advertised(&window);
let policy = super::super::freshness_policy();
let now = chrono::SubsecRound::trunc_subsecs(chrono::Utc::now(), 0);
let edge = now - w.max_age - w.clock_skew;
let mut doc = query(serde_json::json!({}));
doc.issued_at = Some(edge);
assert!(doc.validate_freshness(now, &policy).is_ok());
doc.issued_at = Some(edge - chrono::TimeDelta::seconds(1));
assert!(doc.validate_freshness(now, &policy).is_err());
}
#[test]
fn the_answer_lists_what_the_dispatcher_routes() {
let body = answer(handle(&identified(), query(serde_json::json!({}))));
wire::Response::validate_value(&body["payload"]).expect("schema-valid 0.3 response");
let listed: Vec<&str> = body["payload"]["supportedTypes"]
.as_array()
.expect("an array")
.iter()
.filter_map(Value::as_str)
.collect();
for uri in served_uris() {
assert!(listed.contains(&uri), "{uri} is routed but not listed");
}
assert!(listed.contains(&DISCOVERY_V0_3_TYPE));
assert!(
crate::rooms::handlers::served_uris()
.iter()
.any(|u| listed.contains(u))
);
}
#[test]
fn an_unidentified_caller_is_refused() {
let ctx = JoinAuthCtx {
transport: JoinTransport::Rest,
sender_did: None,
verified_signer: None,
};
let body = answer(handle(&ctx, query(serde_json::json!({}))));
assert_eq!(body["payload"]["code"], "permissionDenied", "{body}");
}
#[test]
fn a_query_over_the_pattern_bound_is_malformed() {
let patterns: Vec<String> = (0..17).map(|i| format!("p{i}/*")).collect();
let body = answer(handle(
&identified(),
query(serde_json::json!({ "patterns": patterns })),
));
assert_eq!(body["payload"]["code"], "malformedRequest", "{body}");
}
}