AkurAI Build
Menu

Public repository / 4750638e45320e2d44675a0f4312a4a7bbbc1bc6

AkurAI-VPN / crates/akurai-control/src/peermap.rs

Latest change 9b5c4b46c61b3417e20efb18511110ee4a9a2002 - feat: organization-scoped device enrollment and customer-safe VPN health (AKURAIVPN-4) by Ólafur Búi Ólafsson

//! Peer-map generation (control-plane).
//!
//! Builds the per-node view of the network — for a given user, the set of their
//! own [`VpnEndpoint`]s a node may reach, minus the requesting node itself, each
//! tagged with its overlay IPv4, current liveness, and best direct UDP endpoint
//! candidate. The data-plane node polls this to learn its peers. The ACL-filtered
//! watch stream is not implemented in 0.0.1; this is the snapshot endpoint that
//! backs it.

use std::collections::HashMap;

use crate::heartbeat::{self, Heartbeat};
use crate::ipam;
use crate::vpn_endpoint::VpnEndpoint;

/// Build the JSON array of peers visible to one organization/workspace.
///
/// Only active devices in the exact tenant boundary are included. The caller's
/// own node, named by `self_id`, is excluded.
pub fn build_peermap_json(
    endpoints: &[VpnEndpoint],
    heartbeats: &HashMap<String, Heartbeat>,
    organization_id: &str,
    workspace_id: &str,
    self_id: Option<&str>,
    now: u64,
) -> String {
    let peers = endpoints
        .iter()
        .filter(|e| e.is_active() && e.belongs_to(organization_id, workspace_id))
        .filter(|e| self_id != Some(e.id.as_str()))
        .map(|e| peer_json(e, heartbeats, now))
        .collect::<Vec<_>>()
        .join(",");
    format!("[{peers}]\n")
}

/// Serialize one peer to a JSON object string.
fn peer_json(e: &VpnEndpoint, heartbeats: &HashMap<String, Heartbeat>, now: u64) -> String {
    let overlay = ipam::overlay_addr_string(&e.allowed_ips).unwrap_or_default();
    let hb = heartbeats.get(&e.id);
    let online = hb
        .map(|hb| heartbeat::is_online(hb.last_seen, now))
        .unwrap_or(false);
    let endpoint = if online {
        hb.map(|h| h.endpoint.trim()).filter(|s| !s.is_empty())
    } else {
        None
    }
    .or_else(|| {
        let saved = e.endpoint_addr.trim();
        (!saved.is_empty()).then_some(saved)
    })
    .unwrap_or("");
    format!(
        "{{\"overlay_ipv4\":\"{}\",\"public_key\":\"{}\",\"name\":\"{}\",\"online\":{},\"endpoint\":\"{}\"}}",
        json_esc(&overlay),
        json_esc(&e.public_key),
        json_esc(&e.name),
        online,
        json_esc(endpoint),
    )
}

fn json_esc(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('"', "\\\"")
        .replace('\n', "\\n")
        .replace('\r', "\\r")
}

/// One-line status of the peer-map subsystem.
pub fn status() -> String {
    "peermap: per-user peer view active (overlay IP + liveness); watch stream not implemented"
        .to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    fn ep(id: &str, org_id: &str, ws_id: &str, allowed: &[&str]) -> VpnEndpoint {
        VpnEndpoint {
            id: id.to_string(),
            name: format!("name-{id}"),
            public_key: format!("pk-{id}"),
            allowed_ips: allowed.iter().map(|s| s.to_string()).collect(),
            added_by: format!("{org_id}@example.com"),
            added_at: 1,
            organization_id: org_id.to_string(),
            workspace_id: ws_id.to_string(),
            ..Default::default()
        }
    }

    fn fresh(now: u64) -> Heartbeat {
        Heartbeat {
            endpoint: "203.0.113.1:51820".to_string(),
            last_seen: now,
        }
    }

    #[test]
    fn peermap_is_scoped_to_the_user() {
        let endpoints = vec![
            ep("mine", "org-1", "ws-1", &["100.88.0.2/32"]),
            ep("theirs", "org-2", "ws-2", &["100.88.0.3/32"]),
        ];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, 1_000);
        assert!(json.contains("name-mine"));
        // Another tenant's node never appears in the caller's peer map.
        assert!(!json.contains("name-theirs"));
        assert!(!json.contains("100.88.0.3"));
    }

    #[test]
    fn peermap_excludes_the_self_node() {
        let endpoints = vec![
            ep("self", "org-1", "ws-1", &["100.88.0.2/32"]),
            ep("peer", "org-1", "ws-1", &["100.88.0.3/32"]),
        ];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", Some("self"), 1_000);
        assert!(!json.contains("name-self"));
        assert!(!json.contains("100.88.0.2"));
        assert!(json.contains("name-peer"));
        assert!(json.contains("100.88.0.3"));
    }

    #[test]
    fn peermap_no_self_param_excludes_nothing() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, 1_000);
        assert!(json.contains("name-a"));
    }

    #[test]
    fn online_is_false_without_a_heartbeat() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, 1_000);
        assert!(json.contains("\"online\":false"));
    }

    #[test]
    fn online_is_true_with_a_fresh_heartbeat() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let now = 1_000;
        let mut hbs = HashMap::new();
        hbs.insert("a".to_string(), fresh(now));
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, now);
        assert!(json.contains("\"online\":true"));
    }

    #[test]
    fn fresh_heartbeat_endpoint_is_included() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let now = 1_000;
        let mut hbs = HashMap::new();
        hbs.insert(
            "a".to_string(),
            Heartbeat {
                endpoint: "192.168.1.44:51399".to_string(),
                last_seen: now,
            },
        );
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, now);
        assert!(json.contains("\"endpoint\":\"192.168.1.44:51399\""));
    }

    #[test]
    fn stale_heartbeat_endpoint_is_not_included() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let mut hbs = HashMap::new();
        hbs.insert(
            "a".to_string(),
            Heartbeat {
                endpoint: "192.168.1.44:51399".to_string(),
                last_seen: 1_000,
            },
        );
        let json = build_peermap_json(
            &endpoints,
            &hbs,
            "org-1",
            "ws-1",
            None,
            1_001 + heartbeat::HEARTBEAT_TTL_SECS,
        );
        assert!(json.contains("\"endpoint\":\"\""));
    }

    #[test]
    fn online_flips_false_when_heartbeat_is_older_than_ttl() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.2/32"])];
        let last_seen = 1_000;
        let mut hbs = HashMap::new();
        hbs.insert(
            "a".to_string(),
            Heartbeat {
                endpoint: String::new(),
                last_seen,
            },
        );
        // One second past the TTL window -> offline.
        let now = last_seen + heartbeat::HEARTBEAT_TTL_SECS + 1;
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, now);
        assert!(json.contains("\"online\":false"));
    }

    #[test]
    fn peer_json_carries_overlay_ipv4_and_public_key() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["100.88.0.7/32"])];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, 1_000);
        assert!(json.contains("\"overlay_ipv4\":\"100.88.0.7\""));
        assert!(json.contains("\"public_key\":\"pk-a\""));
    }

    #[test]
    fn overlay_ipv4_is_empty_string_when_node_has_no_overlay_address() {
        let endpoints = vec![ep("a", "org-1", "ws-1", &["0.0.0.0/0"])];
        let hbs = HashMap::new();
        let json = build_peermap_json(&endpoints, &hbs, "org-1", "ws-1", None, 1_000);
        assert!(json.contains("\"overlay_ipv4\":\"\""));
    }
}