erbridge_core/reverse/
enroll.rs1use std::time::Duration;
10
11use anyhow::{Context, Result};
12use serde::{Deserialize, Serialize};
13use tokio::net::TcpStream;
14
15use crate::noise;
16use crate::reverse::read_frame;
17
18const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
19const MAX_CREDENTIALS_LEN: usize = 4096;
20
21#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct Credentials {
25 pub name: String,
27 pub token: String,
29 pub sni: String,
31}
32
33impl std::fmt::Debug for Credentials {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("Credentials")
36 .field("name", &self.name)
37 .field("token", &"<redacted>")
38 .field("sni", &self.sni)
39 .finish()
40 }
41}
42
43impl Credentials {
44 pub fn to_toml(&self) -> Result<String> {
45 toml::to_string_pretty(self).context("serialising credentials")
46 }
47
48 pub fn from_toml(text: &str) -> Result<Self> {
49 toml::from_str(text).context("parsing credentials")
50 }
51}
52
53pub async fn enroll(server: &str, code: &str) -> Result<Credentials> {
55 enroll_with_timeout(server, code, DEFAULT_TIMEOUT).await
56}
57
58pub async fn enroll_with_timeout(
60 server: &str,
61 code: &str,
62 timeout: Duration,
63) -> Result<Credentials> {
64 let attempt = async {
65 let tcp = TcpStream::connect(server)
66 .await
67 .with_context(|| format!("connecting to {server}"))?;
68 let _ = tcp.set_nodelay(true);
69 let mut stream = noise::connect(tcp, &noise::derive_psk(code))
70 .await
71 .context("enrollment handshake failed (is the code right?)")?;
72 let frame = read_frame(&mut stream, MAX_CREDENTIALS_LEN)
73 .await
74 .context("server did not return credentials")?;
75 serde_json::from_slice(&frame).context("server sent malformed credentials")
76 };
77 tokio::time::timeout(timeout, attempt)
78 .await
79 .context("timed out waiting for enrollment")?
80}