Skip to main content

Mist/
lib.rs

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