use std::path::Path;
use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use affinidi_did_resolver_cache_sdk::config::DIDCacheConfigBuilder;
use affinidi_did_resolver_cache_sdk::network_resolvers::HostPolicy as CacheHostPolicy;
use affinidi_tdk::secrets_resolver::secrets::Secret;
use vta_sdk::provision_client::EphemeralSetupKey;
use super::trust_tasks::send::{build_signed_request, post_trust_task_https_verified};
use super::trust_tasks::{HostPolicy, TransportBoundVerifier, metrics_request_type_uri};
pub async fn fetch_metrics(
server_did: &str,
key_path: &Path,
url: Option<String>,
default_host: &str,
default_port: u16,
) -> Result<(), Box<dyn std::error::Error>> {
let request_type = metrics_request_type_uri();
let base = url.unwrap_or_else(|| loopback_base_url(default_host, default_port));
let endpoint = format!("{}/api/trust-tasks", base.trim_end_matches('/'));
let key = EphemeralSetupKey::load_from(key_path)?;
let from = key.did.clone();
let mut signer = Secret::from_multibase(key.private_key_multibase(), None)
.map_err(|e| format!("metrics key is not a usable signing key: {e}"))?;
let public_mb = signer
.get_public_keymultibase()
.map_err(|e| format!("metrics key has no usable public key: {e}"))?;
signer.id = format!("{from}#{public_mb}");
let doc = build_signed_request(
request_type,
&from,
server_did,
serde_json::json!({}),
&signer,
)
.await
.map_err(|e| format!("failed to sign the metrics request: {e}"))?;
let resolver = DIDCacheClient::new(
DIDCacheConfigBuilder::default()
.with_host_policy(CacheHostPolicy::AllowPrivate)
.build(),
)
.await
.map_err(|e| format!("could not build a DID resolver: {e}"))?;
let verifier =
TransportBoundVerifier::with_did_cache_and_host_policy(resolver, HostPolicy::AllowPrivate);
let reply = post_trust_task_https_verified(&from, server_did, &endpoint, &doc, &verifier)
.await
.map_err(|e| format!("metrics request to {endpoint} failed: {e}"))?;
let payload: serde_json::Value = crate::witness_client::read_reply(request_type, reply)
.map_err(|e| format!("metrics request to {endpoint}: {e}"))?;
let counters = payload
.get("snapshot")
.and_then(|snapshot| snapshot.get("counters"))
.filter(|counters| counters.is_array())
.ok_or_else(|| {
format!(
"metrics response from {server_did} carried no snapshot.counters array: {}",
serde_json::to_string(&payload).unwrap_or_default()
)
})?;
println!("{}", serde_json::to_string(counters)?);
Ok(())
}
fn loopback_base_url(host: &str, port: u16) -> String {
match host.parse::<std::net::IpAddr>() {
Ok(ip) => {
let dial = match ip {
std::net::IpAddr::V4(v4) if v4.is_unspecified() => {
std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
}
std::net::IpAddr::V6(v6) if v6.is_unspecified() => {
std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST)
}
other => other,
};
format!("http://{}", std::net::SocketAddr::new(dial, port))
}
Err(_) => format!("http://{host}:{port}"),
}
}
#[cfg(test)]
mod tests {
use super::loopback_base_url;
#[test]
fn the_metrics_response_type_is_the_request_type_with_a_response_fragment() {
use trust_tasks_rs::Payload;
use trust_tasks_rs::specs::did_management::server::metrics::v0_1;
assert_eq!(
format!("{}#response", super::metrics_request_type_uri()),
<v0_1::Response as Payload>::TYPE_URI
);
}
#[test]
fn maps_wildcard_binds_to_loopback() {
assert_eq!(loopback_base_url("0.0.0.0", 8530), "http://127.0.0.1:8530");
assert_eq!(loopback_base_url("::", 8530), "http://[::1]:8530");
}
#[test]
fn brackets_ipv6_literals_and_leaves_others_verbatim() {
assert_eq!(loopback_base_url("::1", 8530), "http://[::1]:8530");
assert_eq!(
loopback_base_url("2001:db8::1", 443),
"http://[2001:db8::1]:443"
);
assert_eq!(
loopback_base_url("127.0.0.1", 8530),
"http://127.0.0.1:8530"
);
assert_eq!(
loopback_base_url("localhost", 8530),
"http://localhost:8530"
);
}
}