Skip to main content

ap_proxy_protocol/
rendezvous.rs

1//! Rendezvous codes for temporary peer discovery.
2//!
3//! The rendezvous system provides short, human-readable codes that clients can share
4//! to discover each other's identities without exchanging long-lived public keys.
5//!
6//! # Overview
7//! 1. Client requests a rendezvous code from the server
8//! 2. Server generates a unique code (e.g., "ABC-DEF-GHI") and maps it to the client's identity
9//! 3. Peer uses the code to look up the client's identity
10//! 4. Server returns the identity and deletes the code (single-use)
11
12use rand::distributions::{Alphanumeric, DistString};
13use serde::{Deserialize, Serialize};
14
15/// A temporary rendezvous code for peer discovery.
16///
17/// Rendezvous codes are short, human-readable identifiers (format: "ABC-DEF-GHI") that
18/// temporarily map to a client's identity on the proxy server.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct RendezvousCode {
21    code: String,
22}
23
24impl Default for RendezvousCode {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl RendezvousCode {
31    /// Generate a new random rendezvous code.
32    ///
33    /// Creates a 9-character code from the alphanumeric alphabet (A-Z, 0-9),
34    /// formatted with hyphen separators (e.g., "ABC-DEF-GHI").
35    ///
36    /// # Entropy
37    ///
38    /// With an alphabet of 36 characters and 9 positions:
39    /// - Total possibilities: 36^9 = 101,559,956,668,416
40    /// - Given the 5-minute lifetime, brute-force is impractical
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use ap_proxy_protocol::RendezvousCode;
46    ///
47    /// let code1 = RendezvousCode::new();
48    /// let code2 = RendezvousCode::new();
49    ///
50    /// // Each call generates a different random code
51    /// assert_ne!(code1.as_str(), code2.as_str());
52    ///
53    /// // Format is always ABC-DEF-GHI style
54    /// assert_eq!(code1.as_str().len(), 11); // 9 chars + 2 hyphens
55    /// assert_eq!(code1.as_str().chars().nth(3), Some('-'));
56    /// assert_eq!(code1.as_str().chars().nth(7), Some('-'));
57    /// ```
58    pub fn new() -> Self {
59        let mut rng = rand::thread_rng();
60
61        // The code has an alphabet of size 36. With 9 characters, that's
62        // 36^9 = 101,559,956,668,416 possible codes. The codes are short-lived, and the connections to the proxy are rate-limited,
63        // which is why this is considered sufficient.
64        let code = Alphanumeric.sample_string(&mut rng, 9);
65        let code = code.to_ascii_uppercase();
66        // SAFETY: Alphanumeric + to_ascii_uppercase produces only ASCII characters,
67        // so indexing at character boundaries is safe.
68        #[allow(clippy::string_slice)]
69        let code = format!("{}-{}-{}", &code[..3], &code[3..6], &code[6..]);
70
71        RendezvousCode { code }
72    }
73
74    /// Create a rendezvous code from an existing string.
75    ///
76    /// Useful for:
77    /// - Testing with known codes
78    /// - Parsing user input
79    /// - Deserializing from storage
80    ///
81    /// No validation is performed - the caller is responsible for ensuring
82    /// the code is in the correct format.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use ap_proxy_protocol::RendezvousCode;
88    ///
89    /// let code = RendezvousCode::from_string("ABC-DEF-GHI".to_string());
90    /// assert_eq!(code.as_str(), "ABC-DEF-GHI");
91    /// ```
92    pub fn from_string(code: String) -> Self {
93        RendezvousCode { code }
94    }
95
96    /// Get the code string.
97    ///
98    /// Returns the formatted code (e.g., "ABC-DEF-GHI") that can be displayed to
99    /// users or sent to the server.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use ap_proxy_protocol::RendezvousCode;
105    ///
106    /// let code = RendezvousCode::new();
107    /// println!("Your code: {}", code.as_str());
108    /// ```
109    pub fn as_str(&self) -> &str {
110        &self.code
111    }
112}
113
114impl std::fmt::Display for RendezvousCode {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", self.code)
117    }
118}