Skip to main content

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}