hightower_node/
lib.rs

1pub mod certificates;
2pub mod context;
3
4use context::CommonContext;
5use hightower_client::HightowerConnection;
6use tracing::{debug, error};
7
8const GATEWAY_URL_KEY: &[u8] = b"gateway/url";
9
10pub async fn run(context: &CommonContext) -> Result<HightowerConnection, String> {
11    // Get gateway URL from context or use default
12    let gateway_url = context
13        .kv
14        .get_bytes(GATEWAY_URL_KEY)
15        .ok()
16        .flatten()
17        .and_then(|bytes| String::from_utf8(bytes).ok())
18        .unwrap_or_else(|| "http://127.0.0.1:8008".to_string());
19
20    // Get auth token from context
21    let auth_token = context
22        .kv
23        .get_bytes(context::HT_AUTH_KEY)
24        .map_err(|e| format!("Failed to get auth token: {:?}", e))?
25        .ok_or_else(|| "Auth token not found in context".to_string())?;
26
27    let auth_token = String::from_utf8(auth_token)
28        .map_err(|e| format!("Invalid auth token encoding: {:?}", e))?;
29
30    debug!(gateway_url = %gateway_url, "Connecting to gateway");
31
32    // Connect using hightower-client - handles everything:
33    // - Keypair generation
34    // - Transport server creation
35    // - IP discovery via STUN
36    // - Gateway registration
37    // - Peer management
38    // - Persistence
39    let connection = HightowerConnection::connect(&gateway_url, &auth_token)
40        .await
41        .map_err(|e| {
42            error!(error = ?e, "Failed to connect to gateway");
43            format!("Failed to connect to gateway: {:?}", e)
44        })?;
45
46    debug!(
47        node_id = %connection.node_id(),
48        assigned_ip = %connection.assigned_ip(),
49        "Connected to gateway"
50    );
51
52    // Ping gateway to verify WireGuard connectivity
53    if let Err(e) = connection.ping_gateway().await {
54        error!(error = ?e, "Failed to ping gateway over WireGuard");
55    } else {
56        debug!("Successfully pinged gateway over WireGuard");
57    }
58
59    Ok(connection)
60}
61
62pub async fn deregister(connection: HightowerConnection) -> Result<(), String> {
63    connection
64        .disconnect()
65        .await
66        .map_err(|e| format!("Failed to disconnect: {:?}", e))
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use context::fixtures;
73
74    #[tokio::test]
75    async fn run_connects_to_gateway() {
76        let ctx = fixtures::context();
77        ctx.kv.put_secret(context::HT_AUTH_KEY, b"test-auth-key");
78
79        // Note: This test will fail without a running gateway
80        // In a real test environment, you'd mock the gateway or skip this test
81        match run(&ctx).await {
82            Ok(connection) => {
83                assert!(!connection.node_id().is_empty());
84                assert!(!connection.assigned_ip().is_empty());
85
86                // Clean up
87                let _ = deregister(connection).await;
88            }
89            Err(e) => {
90                // Expected if no gateway is running
91                assert!(e.contains("Failed to connect"));
92            }
93        }
94    }
95}