use std::sync::Arc;
use std::time::Duration;
use affinidi_tdk::messaging::ATM;
use affinidi_tdk::messaging::profiles::ATMProfile;
use tokio::sync::watch;
use tracing::{info, warn};
use crate::messaging::auth::auth_from_did;
use crate::server::AppState;
const BACKOFF_INITIAL: Duration = Duration::from_secs(5);
const BACKOFF_MAX: Duration = Duration::from_secs(60);
pub async fn dispatch_one(app_state: &AppState, payload: &[u8], sender_vid: &str) {
match auth_from_did(sender_vid, &app_state.acl_ks).await {
Ok(auth) => {
let outcome =
crate::trust_tasks::dispatch_trust_task_core(app_state, &auth, payload).await;
info!(
sender = %sender_vid,
status = %outcome.status,
"TSP trust-task dispatched"
);
}
Err(e) => {
warn!(
sender = %sender_vid,
error = %e,
"TSP message from unauthorized sender — dropped"
);
}
}
}
pub async fn run_tsp_inbound(
app_state: AppState,
atm: ATM,
profile: Arc<ATMProfile>,
mut shutdown: watch::Receiver<bool>,
) {
info!("TSP inbound loop starting");
let mut backoff = BACKOFF_INITIAL;
loop {
if *shutdown.borrow() {
info!("TSP inbound loop shutting down");
return;
}
match atm.tsp().connect_websocket(&profile).await {
Ok(mut ws) => {
info!("TSP inbound websocket connected");
backoff = BACKOFF_INITIAL;
loop {
tokio::select! {
_ = shutdown.changed() => {
if *shutdown.borrow() {
info!("TSP inbound loop shutting down");
return;
}
}
r = ws.recv() => match r {
Ok(Some(qb2)) => {
match atm.tsp().unpack_bytes(&profile, &qb2).await {
Ok((payload, sender)) => {
dispatch_one(&app_state, &payload, &sender).await;
}
Err(e) => {
warn!(error = %e, "TSP unpack failed — dropping frame");
}
}
}
Ok(None) => {
info!("TSP inbound websocket closed — reconnecting");
break;
}
Err(e) => {
warn!(error = %e, "TSP inbound recv error — reconnecting");
break;
}
}
}
}
}
Err(e) => {
warn!(
error = %e,
backoff_secs = backoff.as_secs(),
"TSP inbound connect failed — backing off"
);
tokio::select! {
_ = shutdown.changed() => {
if *shutdown.borrow() {
info!("TSP inbound loop shutting down");
return;
}
}
_ = tokio::time::sleep(backoff) => {}
}
backoff = (backoff * 2).min(BACKOFF_MAX);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::acl::{AclEntry, Role, store_acl_entry};
use crate::test_support::build_signing_test_app_state;
#[tokio::test]
async fn dispatch_one_unknown_sender_drops_without_panic() {
let (app_state, _dir) = build_signing_test_app_state().await;
dispatch_one(&app_state, b"{}", "did:key:zUnauthorizedTspSender").await;
}
#[tokio::test]
async fn dispatch_one_authorized_sender_reaches_spine() {
let (app_state, _dir) = build_signing_test_app_state().await;
let did = "did:key:zAuthorizedTspSender";
store_acl_entry(&app_state.acl_ks, &AclEntry::new(did, Role::Admin, "test"))
.await
.unwrap();
dispatch_one(&app_state, b"{}", did).await;
}
}