Mist/Library.rs
1#![allow(
2 non_snake_case,
3 non_camel_case_types,
4 non_upper_case_globals,
5 dead_code,
6 unused_imports,
7 unused_variables,
8 unused_assignments,
9 clippy::tabs_in_doc_comments,
10 clippy::unnecessary_lazy_evaluations
11)]
12//! # Mist: Private DNS for Local-First Networking
13//! Mist gives Land its own private DNS so editor components can find each
14//! other on `*.land.playform.cloud` without touching the public internet. All
15//! queries resolve to `127.0.0.1`. No external DNS leaks, no configuration
16//! needed.
17//!
18//! ## Features
19//!
20//! - **Private DNS Zone**: Authoritative zone for `*.land.playform.cloud`
21//! domains
22//! - **Local Resolution**: All land.playform.cloud queries resolve to
23//! `127.0.0.1`.
24//! - **Dynamic Port Allocation**: Automatically finds available ports using
25//! portpicker
26//! - **Async/Sync Support**: Both async and blocking server implementations
27//!
28//! ## Example
29//!
30//! ```rust,no_run
31//! use Mist::start;
32//!
33//! #[tokio::main]
34//! async fn main() -> anyhow::Result<()> {
35//! // Start the DNS server (tries port 5353 first, then finds an available one)
36//! let port = start(5353)?;
37//!
38//! println!("DNS server running on port {}", port);
39//!
40//! // The server runs in the background
41//! // Use DNS_PORT to get the port number elsewhere
42//!
43//! Ok(())
44//! }
45//! ```
46
47use std::thread;
48
49use anyhow::Result;
50use once_cell::sync::OnceCell;
51
52// Public module exports (PascalCase per project convention)
53pub mod Server;
54
55pub mod Zone;
56
57pub mod Resolver;
58
59pub mod ForwardSecurity;
60
61// LAND-PATCH B7-S6 P1: WebSocket transport for the Sky↔Cocoon
62pub mod WebSocket;
63
64/// Global DNS port number.
65///
66/// This static cell stores the port number that the DNS server is running on.
67/// It is set once when [`start`] is called and remains constant thereafter.
68///
69/// # Example
70///
71/// ```rust
72/// use Mist::dns_port;
73///
74/// // Returns the port number, or 0 if the server hasn't been started
75/// let port = dns_port();
76/// ```
77pub static DNS_PORT:OnceCell<u16> = OnceCell::new();
78
79/// Returns the DNS port number.
80///
81/// Returns the port that the DNS server is listening on, or `0` if the
82/// server has not been started yet.
83///
84/// # Returns
85///
86/// The port number (0-65535), or 0 if the server hasn't started.
87///
88/// # Example
89///
90/// ```rust
91/// use Mist::dns_port;
92///
93/// let port = dns_port();
94/// if port > 0 {
95/// println!("DNS server is running on port {}", port);
96/// } else {
97/// println!("DNS server has not been started");
98/// }
99/// ```
100pub fn dns_port() -> u16 { *DNS_PORT.get().unwrap_or(&0) }
101
102/// Starts the DNS server for the CodeEditorLand private network.
103///
104/// This function performs the following steps:
105/// 1. Uses portpicker to find an available port (tries `preferred_port` first)
106/// 2. Sets the `DNS_PORT` global variable
107/// 3. Builds the DNS catalog with the `land.playform.cloud` zone
108/// 4. Spawns the DNS server as a background task
109/// 5. Returns the port number
110///
111/// The DNS server runs in the background and can be stopped by dropping
112/// the application.
113///
114/// # Parameters
115///
116/// * `preferred_port` - The preferred port number to use. If this port is
117/// already in use, portpicker will find an alternative available port.
118///
119/// # Returns
120///
121/// Returns `Ok(port)` with the port number the server is listening on,
122/// or an error if the server failed to start.
123///
124/// # Example
125///
126/// ```rust,no_run
127/// use Mist::start;
128///
129/// #[tokio::main]
130/// async fn main() -> anyhow::Result<()> {
131/// // Start DNS server, preferring port 5353
132/// let port = start(5353)?;
133/// println!("DNS server started on port {}", port);
134/// tokio::signal::ctrl_c().await?;
135/// Ok(())
136/// }
137/// ```
138pub fn start(preferred_port:u16) -> Result<u16> {
139 // Step 1: Find an available port using portpicker
140 // Try the preferred port first, then pick a random available one
141 let port = portpicker::pick_unused_port()
142 .or_else(|| {
143 // If pick_unused_port returns None, try the preferred port explicitly
144 Some(preferred_port)
145 })
146 .ok_or_else(|| anyhow::anyhow!("Failed to find an available port"))?;
147
148 // Step 2: Set the DNS_PORT globally
149 DNS_PORT
150 .set(port)
151 .map_err(|_| anyhow::anyhow!("DNS port has already been set"))?;
152
153 // Step 3: Build the DNS catalog
154 let catalog = Server::BuildCatalog(port)?;
155
156 // Step 4: Spawn the DNS server as a background task
157 thread::spawn(move || {
158 if let Err(e) = Server::ServeSync(catalog, port) {
159 eprintln!("DNS server error: {:?}", e);
160 }
161 });
162
163 // Step 5: Return the port number
164 Ok(port)
165}
166
167#[cfg(test)]
168mod tests {
169
170 use super::*;
171
172 #[test]
173 fn test_dns_port_initial_state() {
174 // Initially, DNS_PORT should be 0
175 let port = dns_port();
176
177 assert_eq!(port, 0);
178 }
179
180 #[test]
181 fn test_dns_port_starts_server() {
182 // Test that we can start the DNS server
183 let preferred_port = 15353; // Use a non-standard port for testing
184
185 let result = start(preferred_port);
186
187 // The server should start successfully
188 assert!(result.is_ok(), "Failed to start DNS server");
189
190 let port = result.unwrap();
191
192 // The port should be within valid range
193 assert!(port >= 1024, "Port should be >= 1024");
194
195 assert!(port <= 65535, "Port should be <= 65535");
196
197 // DNS_PORT should now return the same port
198 let retrieved_port = dns_port();
199
200 assert_eq!(port, retrieved_port, "DNS_PORT should match returned port");
201 }
202
203 #[test]
204 fn test_start_fails_on_second_call() {
205 // Starting the server twice should fail
206 let port1 = start(15354);
207
208 assert!(port1.is_ok(), "First start should succeed");
209
210 let port2 = start(15355);
211
212 assert!(port2.is_err(), "Second start should fail");
213 }
214
215 #[test]
216 fn test_build_catalog_api() {
217 let catalog = Server::BuildCatalog(15356);
218
219 assert!(catalog.is_ok(), "Should be able to build catalog");
220 }
221
222 #[test]
223 fn test_build_zone_api() {
224 let zone = Zone::EditorLandZone();
225
226 assert!(zone.is_ok(), "Should be able to build zone");
227 }
228}