Skip to main content

volt_client_grpc/
utils.rs

1//! Utility functions for the Volt client.
2
3use crate::constants::DEFAULT_DID_HOST_NAME;
4use crate::error::{Result, VoltError};
5
6/// Create TLS configuration options from PEM-encoded certificates and keys
7pub struct TlsOptions {
8    pub ca: Vec<u8>,
9    pub cert: Option<Vec<u8>>,
10    pub key: Option<Vec<u8>>,
11}
12
13impl TlsOptions {
14    /// Create TLS options from PEM strings
15    pub fn from_pem(ca_pem: &str, cert_pem: Option<&str>, key_pem: Option<&str>) -> Self {
16        Self {
17            ca: ca_pem.as_bytes().to_vec(),
18            cert: cert_pem.map(|s| s.as_bytes().to_vec()),
19            key: key_pem.map(|s| s.as_bytes().to_vec()),
20        }
21    }
22}
23
24/// Get the service address from an mDNS service record
25pub fn get_service_address(host: &str, port: u16) -> String {
26    format!("{}:{}", host, port)
27}
28
29/// Build a DID resolution URL
30pub fn get_did_resolution_url(did: &str, registry_url: Option<&str>) -> Result<String> {
31    let did_lower = did.to_lowercase();
32    let parts: Vec<&str> = did_lower.split(':').collect();
33
34    if parts.len() < 3 {
35        return Err(VoltError::InvalidArgument("Invalid DID format".into()));
36    }
37
38    if parts[0] != "did" {
39        return Err(VoltError::InvalidArgument("Not a valid DID".into()));
40    }
41
42    if parts[1] != "volt" {
43        return Err(VoltError::InvalidArgument("Not a Volt DID".into()));
44    }
45
46    let lookup_url = match registry_url {
47        Some(url) if url.starts_with("http") => url.to_string(),
48        Some(url) => format!("https://{}", url),
49        None => format!("https://{}", DEFAULT_DID_HOST_NAME),
50    };
51
52    Ok(format!("{}/did/{}", lookup_url, did))
53}
54
55/// Get the local IP address
56pub fn get_local_ip() -> Result<String> {
57    local_ip_address::local_ip()
58        .map(|ip| ip.to_string())
59        .map_err(|e| VoltError::internal(format!("Failed to get local IP: {}", e)))
60}
61
62/// Remove carriage returns from a string (useful for PEM processing)
63pub fn remove_carriage_return(s: &str) -> String {
64    s.replace('\r', "")
65}
66
67/// Check if an IP string is IPv4 format
68pub fn is_ipv4(addr: &str) -> bool {
69    addr.parse::<std::net::Ipv4Addr>().is_ok()
70}
71
72/// Check if an IP string is IPv6 format
73pub fn is_ipv6(addr: &str) -> bool {
74    addr.parse::<std::net::Ipv6Addr>().is_ok()
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_did_resolution_url() {
83        let url = get_did_resolution_url("did:volt:test123", None).unwrap();
84        assert!(url.contains("coreid.com"));
85        assert!(url.contains("did:volt:test123"));
86
87        let url = get_did_resolution_url("did:volt:test123", Some("https://example.com")).unwrap();
88        assert!(url.starts_with("https://example.com"));
89    }
90
91    #[test]
92    fn test_invalid_did() {
93        assert!(get_did_resolution_url("invalid", None).is_err());
94        assert!(get_did_resolution_url("did:other:test", None).is_err());
95    }
96
97    #[test]
98    fn test_ip_detection() {
99        assert!(is_ipv4("192.168.1.1"));
100        assert!(!is_ipv4("::1"));
101        assert!(is_ipv6("::1"));
102        assert!(is_ipv6("2001:db8::1"));
103    }
104}