AirLibrary/
Library.rs

1#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
2#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
3
4//! # Air Library
5//!
6//! ## Overview
7//!
8//! The Air Library is the core implementation for the Air daemon - the
9//! persistent background service for the Land code editor. It provides services
10//! for authentication, updates, downloads, and file indexing that run
11//! independently from the main Mountain application.
12//!
13//! ## Architecture & Connections
14//!
15//! Air is the hub that connects various components in the Land ecosystem:
16//!
17//! - **Wind** (Effect-TS): Functional programming patterns for state management
18//!   Air integrates with Wind's effect system for predictable state transitions
19//!   and error handling patterns
20//!
21//! - **Cocoon** (NodeJS host): The Node.js runtime for web components Air
22//!   communicates with Cocoon through the Vine protocol to deliver web assets
23//!   and perform frontend build operations. Port: 50052
24//!
25//! - **Mountain** (Tauri bundler): Main desktop application Mountain receives
26//!   work from Air through Vine (gRPC) and performs the main application logic.
27//!   Mountain's Tauri framework handles the native integration
28//!
29//! - **Vine** (gRPC protocol): Communication layer connecting all components
30//!   Air hosts the Vine gRPC server on port 50053, receiving work requests from
31//!   Mountain
32//!
33//! ## VSCode Architecture References
34//!
35//! ### Update Service
36//!
37//! Reference: `Dependency/Microsoft/Dependency/Editor/src/vs/platform/update/`
38//!
39//! Air's UpdateManager is inspired by VSCode's update architecture:
40//!
41//! - **AbstractUpdateService** (`common/update.ts`): Base service defining
42//!   update interfaces
43//! - Platform-specific implementations:
44//!   - `updateService.darwin.ts` - macOS update handling
45//!   - `updateService.linux.ts` - Linux update handling
46//!   - `updateService.snap.ts` - Snap package updates
47//!   - `updateService.win32.ts` - Windows update handling
48//!
49//! Air's UpdateManager abstracts platform differences and provides:
50//! - Update checking with version comparison
51//! - Package download with resumable support
52//! - Checksum verification for integrity
53//! - Signature validation for security
54//! - Staged updates for rollback capability
55//!
56//! ### Lifecycle Management
57//!
58//! Reference:
59//! `Dependency/Microsoft/Dependency/Editor/src/vs/base/common/lifecycle.ts`
60//!
61//! VSCode's lifecycle patterns inform Air's daemon management:
62//!
63//! - **Disposable pattern**: Resources implement cleanup methods
64//! - **EventEmitter**: Async event handling for state changes
65//! - **DisposableStore**: Aggregate resource cleanup
66//!
67//! Air adapts these patterns with:
68//! - `ApplicationState`: Central state management with cleanup
69//! - `DaemonManager`: Single-instance lock management
70//! - Graceful shutdown with resource release
71//!
72//! ## Module Organization
73//!
74//! The Air library is organized into functional modules:
75//!
76//! ### Core Infrastructure
77//! - `ApplicationState`: Central state manager for the daemon
78//! - `Configuration`: Configuration loading and validation
79//! - `Daemon`: Daemon lifecycle and lock management
80//! - `Logging`: Structured logging with filtering
81//! - `Metrics`: Prometheus-style metrics collection
82//! - `Tracing`: Distributed tracing support
83//!
84//! ### Services
85//! - `Authentication`: Token management and cryptographic operations
86//! - `Updates`: Update checking, downloading, and installation
87//! - `Downloader`: Background downloads with retry logic
88//! - `Indexing`: File system indexing for code navigation
89//!
90//! ### Communication
91//! - `Vine`: gRPC server and client implementation
92//!   - Generated protobuf code in `Vine/Generated/`
93//!   - Server implementation in `Vine/Server/`
94//!   - Client utilities in `Vine/Client/`
95//!
96//! ### Reliability
97//! - `Resilience`: Retry policies, circuit breakers, timeouts
98//!   - `RetryPolicy`: Configurable retry strategies
99//!   - `CircuitBreaker`: Fail-fast for external dependencies
100//!   - `BulkheadExecutor`: Concurrency limiting
101//!   - `TimeoutManager`: Operation timeout management
102//! - `Security`: Rate limiting, checksums, secure storage
103//! - `HealthCheck`: Service health monitoring
104//!
105//! ### Extensibility
106//! - `Plugins`: Hot-reloadable plugin system
107//! - `CLI`: Command-line interface for daemon control
108//!
109//! ## Protocol Details
110//!
111//! **Vine Protocol (gRPC)**
112//! - **Version**: 1 (Air::ProtocolVersion)
113//! - **Transport**: HTTP/2
114//! - **Serialization**: Protocol Buffers
115//! - **Ports**:
116//!   - 50053: Air (background services) - DefaultBindAddress
117//!   - 50052: Cocoon (NodeJS/web services)
118//!
119//! TODO: Add TLS/mTLS support for production security
120//! ## TODO: Missing Functionality
121//!
122//! ### High Priority
123//! - [ ] Implement metrics HTTP endpoint (/metrics)
124//! - [ ] Add Prometheus metric export with labels
125//! - [ ] Implement TLS/mTLS for gRPC connections
126//! - [ ] Add connection authentication/authorization
127//! - [ ] Implement configuration hot-reload (SIGHUP)
128//! - [ ] Add comprehensive integration tests
129//! - [ ] Implement graceful shutdown with operation completion
130//!
131//! ### Medium Priority
132//! - [ ] Implement plugin hot-reload
133//! - [ ] Add structured logging with correlation IDs
134//! - [ ] Implement distributed tracing (OpenTelemetry)
135//! - [ ] Add health check HTTP endpoint for load balancers
136//! - [ ] Implement connection pooling optimizations
137//! - [ ] Add metrics export to external systems
138//! - [ ] Implement telemetry/observability export
139//!
140//! ### Low Priority
141//! - [ ] Add A/B testing framework for features
142//! - [ ] Implement query optimizer for file index
143//! - [ ] Add caching layer for frequently accessed data
144//! - [ ] Implement adaptive timeout based on load
145//! - [ ] Add predictive scaling based on metrics
146//! - [ ] Implement chaos testing/metrics
147//!
148//! ## Error Handling Strategy
149//!
150//! All modules use defensive coding practices:
151//!
152//! 1. **Input Validation**: All public functions validate inputs with
153//!    descriptive errors
154//! 2. **Timeout Handling**: Default timeouts with configuration overrides
155//! 3. **Resource Cleanup**: Drop trait + explicit cleanup methods
156//! 4. **Circuit Breaker**: Fail-fast for external dependencies
157//! 5. **Retry Logic**: Exponential backoff for transient failures
158//! 6. **Metrics Recording**: All operations record success/failure metrics
159//! 7. **Panic Recovery**: Catch panics in critical async tasks
160//!
161//! ## Constants
162//!
163//! - **VERSION**: Air daemon version from Cargo.toml
164//! - **DefaultConfigFile**: Default config filename (Air.toml)
165//! - **DefaultBindAddress**: gRPC bind address ([::1]:50053)
166//! - **ProtocolVersion**: Vine protocol version (1)
167
168pub mod ApplicationState;
169pub mod Authentication;
170pub mod CLI;
171pub mod Configuration;
172pub mod Daemon;
173pub mod Downloader;
174pub mod HealthCheck;
175pub mod Indexing;
176pub mod Logging;
177pub mod Metrics;
178pub mod Plugins;
179pub mod Resilience;
180pub mod Security;
181pub mod Tracing;
182pub mod Updates;
183pub mod Vine;
184
185/// Air Daemon version information
186///
187/// This is automatically populated from Cargo.toml at build time
188pub const VERSION:&str = env!("CARGO_PKG_VERSION");
189
190/// Default configuration file name
191///
192/// The daemon searches for this configuration file in:
193/// 1. The path specified via --config flag
194/// 2. ~/.config/Air/Air.toml
195/// 3. /etc/Air/Air.toml
196/// 4. Working directory (Air.toml)
197pub const DefaultConfigFile:&str = "Air.toml";
198
199/// Default gRPC bind address for the Vine server
200///
201/// Note: Port 50053 is used for Air to avoid conflict with Cocoon (port 50052)
202///
203/// Addresses in order of preference:
204/// - `--bind` flag value (if provided)
205/// - DefaultBindAddress constant: [::1]:50053
206///
207/// TODO: Add support for:
208/// - IPv4-only binding (0.0.0.0:50053)
209/// - IPv6-only binding ([::]:50053)
210/// - Wildcard binding for all interfaces
211pub const DefaultBindAddress:&str = "[::1]:50053";
212
213/// Protocol version for Mountain-Air communication
214///
215/// This version is sent in all gRPC messages and checked by clients
216/// to ensure compatibility. Increment this value when breaking
217/// protocol changes are made.
218///
219/// Version history:
220/// - 1: Initial Vine protocol
221///
222/// TODO: Implement protocol version checking and negotiation
223pub const ProtocolVersion:u32 = 1;
224
225/// Error type for Air operations
226///
227/// Comprehensive error types for all Air operations with descriptive messages.
228/// All error variants include context to help with debugging and error
229/// recovery.
230///
231/// TODO: Add error codes for programmatic error handling
232/// TODO: Implement error chaining with source tracking
233/// TODO: Add structured error serialization for logging
234/// TODO: Implement error metrics collection
235#[derive(Debug, thiserror::Error, Clone)]
236pub enum AirError {
237	#[error("Configuration error: {0}")]
238	Configuration(String),
239
240	#[error("Authentication error: {0}")]
241	Authentication(String),
242
243	#[error("Network error: {0}")]
244	Network(String),
245
246	#[error("File system error: {0}")]
247	FileSystem(String),
248
249	#[error("gRPC error: {0}")]
250	Grpc(String),
251
252	#[error("Serialization error: {0}")]
253	Serialization(String),
254
255	#[error("Internal error: {0}")]
256	Internal(String),
257
258	#[error("Resource limit exceeded: {0}")]
259	ResourceLimit(String),
260
261	#[error("Service unavailable: {0}")]
262	ServiceUnavailable(String),
263
264	#[error("Validation error: {0}")]
265	Validation(String),
266
267	#[error("Timeout error: {0}")]
268	Timeout(String),
269
270	#[error("Plugin error: {0}")]
271	Plugin(String),
272
273	#[error("Hot-reload error: {0}")]
274	HotReload(String),
275
276	#[error("Connection error: {0}")]
277	Connection(String),
278
279	#[error("Rate limit exceeded: {0}")]
280	RateLimit(String),
281
282	#[error("Circuit breaker open: {0}")]
283	CircuitBreaker(String),
284}
285
286impl From<config::ConfigError> for AirError {
287	fn from(err:config::ConfigError) -> Self { AirError::Configuration(err.to_string()) }
288}
289
290impl From<reqwest::Error> for AirError {
291	fn from(err:reqwest::Error) -> Self { AirError::Network(err.to_string()) }
292}
293
294impl From<std::io::Error> for AirError {
295	fn from(err:std::io::Error) -> Self { AirError::FileSystem(err.to_string()) }
296}
297
298impl From<tonic::transport::Error> for AirError {
299	fn from(err:tonic::transport::Error) -> Self { AirError::Grpc(err.to_string()) }
300}
301
302impl From<serde_json::Error> for AirError {
303	fn from(err:serde_json::Error) -> Self { AirError::Serialization(err.to_string()) }
304}
305
306impl From<toml::de::Error> for AirError {
307	fn from(err:toml::de::Error) -> Self { AirError::Serialization(err.to_string()) }
308}
309
310impl From<uuid::Error> for AirError {
311	fn from(err:uuid::Error) -> Self { AirError::Internal(format!("UUID error: {}", err)) }
312}
313
314impl From<tokio::task::JoinError> for AirError {
315	fn from(err:tokio::task::JoinError) -> Self { AirError::Internal(format!("Task join error: {}", err)) }
316}
317
318impl From<&str> for AirError {
319	fn from(err:&str) -> Self { AirError::Internal(err.to_string()) }
320}
321
322impl From<String> for AirError {
323	fn from(err:String) -> Self { AirError::Internal(err) }
324}
325
326impl From<(crate::HealthCheck::HealthStatus, Option<String>)> for AirError {
327	fn from((status, message):(crate::HealthCheck::HealthStatus, Option<String>)) -> Self {
328		let msg = message.unwrap_or_else(|| format!("Health check failed: {:?}", status));
329		AirError::ServiceUnavailable(msg)
330	}
331}
332
333/// Result type for Air operations
334///
335/// Convenience type alias for Result<T, AirError>
336pub type Result<T> = std::result::Result<T, AirError>;
337
338/// Common utility functions
339///
340/// These utilities provide defensive helper functions used throughout
341/// the Air library for validation, ID generation, timestamp handling,
342/// and common operations with proper error handling.
343pub mod Utility {
344	use super::*;
345
346	/// Generate a unique request ID
347	///
348	/// Creates a UUID v4 for tracing and correlation of requests.
349	/// The ID is guaranteed to be unique (with extremely high probability).
350	///
351	/// TODO: Replace with ULID for sortable IDs
352	/// TODO: Add optional prefix for service identification
353	pub fn GenerateRequestId() -> String { uuid::Uuid::new_v4().to_string() }
354
355	/// Generate a unique request ID with a prefix
356	///
357	/// Format: `{prefix}-{uuid}`
358	///
359	/// # Arguments
360	///
361	/// * `prefix` - Prefix to add before the UUID (e.g., "auth", "download")
362	///
363	/// # Example
364	///
365	/// ```
366	/// let id = GenerateRequestIdWithPrefix("auth");
367	/// // Returns: "auth-550e8400-e29b-41d4-a716-446655440000"
368	/// ```
369	pub fn GenerateRequestIdWithPrefix(Prefix:&str) -> String { format!("{}-{}", Prefix, uuid::Uuid::new_v4()) }
370
371	/// Get current timestamp in milliseconds since UNIX epoch
372	///
373	/// Returns the number of milliseconds since January 1, 1970 00:00:00 UTC.
374	/// Returns 0 if the system time is not available or is before the epoch.
375	pub fn CurrentTimestamp() -> u64 {
376		std::time::SystemTime::now()
377			.duration_since(std::time::UNIX_EPOCH)
378			.unwrap_or_default()
379			.as_millis() as u64
380	}
381
382	/// Get current timestamp in seconds since UNIX epoch
383	pub fn CurrentTimestampSeconds() -> u64 {
384		std::time::SystemTime::now()
385			.duration_since(std::time::UNIX_EPOCH)
386			.unwrap_or_default()
387			.as_secs()
388	}
389
390	/// Convert timestamp millis to ISO 8601 string
391	///
392	/// # Arguments
393	///
394	/// * `millis` - Timestamp in milliseconds since UNIX epoch
395	///
396	/// # Returns
397	///
398	/// ISO 8601 formatted string or "Invalid timestamp" on error
399	pub fn TimestampToISO8601(Millis:u64) -> String {
400		match std::time::UNIX_EPOCH.checked_add(std::time::Duration::from_millis(Millis)) {
401			Some(Time) => {
402				use std::time::SystemTime;
403				match SystemTime::try_from(Time) {
404					Ok(SystemTime) => {
405						let DateTime:chrono::DateTime<chrono::Utc> = SystemTime.into();
406						DateTime.to_rfc3339()
407					},
408					Err(_) => "Invalid timestamp".to_string(),
409				}
410			},
411			None => "Invalid timestamp".to_string(),
412		}
413	}
414
415	/// Validate file path security
416	///
417	/// Checks for path traversal attempts and invalid characters.
418	/// This is a security measure to prevent directory traversal attacks.
419	///
420	/// # Arguments
421	///
422	/// * `path` - The file path to validate
423	///
424	/// # Errors
425	///
426	/// Returns an error if the path contains suspicious patterns.
427	///
428	/// TODO: Add platform-specific validation (Windows paths)
429	/// TODO: Add maximum path length validation
430	pub fn ValidateFilePath(Path:&str) -> Result<()> {
431		// Null check
432		if Path.is_empty() {
433			return Err(AirError::Validation("Path is empty".to_string()));
434		}
435
436		// Length check
437		if Path.len() > 4096 {
438			return Err(AirError::Validation("Path too long (max: 4096 characters)".to_string()));
439		}
440
441		// Path traversal check
442		if Path.contains("..") {
443			return Err(AirError::Validation(
444				"Path contains '..' (potential path traversal)".to_string(),
445			));
446		}
447
448		// Platform-specific checks
449		if cfg!(windows) {
450			// Additional Windows-specific checks could be added here
451		} else if Path.contains('\\') {
452			// On Unix, backslashes are unusual
453			return Err(AirError::Validation("Path contains backslash on Unix".to_string()));
454		}
455
456		// Null character check
457		if Path.contains('\0') {
458			return Err(AirError::Validation("Path contains null character".to_string()));
459		}
460
461		Ok(())
462	}
463
464	/// Validate URL format
465	///
466	/// Performs basic URL validation to prevent malformed URLs from
467	/// causing issues with network operations.
468	///
469	/// # Arguments
470	///
471	/// * `url` - The URL to validate
472	///
473	/// # Errors
474	///
475	/// Returns an error if the URL is invalid.
476	///
477	/// TODO: Use url crate for full RFC 3986 validation
478	pub fn ValidateUrl(URL:&str) -> Result<()> {
479		// Null check
480		if URL.is_empty() {
481			return Err(AirError::Validation("URL is empty".to_string()));
482		}
483
484		// Length check
485		if URL.len() > 2048 {
486			return Err(AirError::Validation("URL too long (max: 2048 characters)".to_string()));
487		}
488
489		// Basic scheme check
490		if !URL.starts_with("http://") && !URL.starts_with("https://") {
491			return Err(AirError::Validation("URL must start with http:// or https://".to_string()));
492		}
493
494		// Null character check
495		if URL.contains('\0') {
496			return Err(AirError::Validation("URL contains null character".to_string()));
497		}
498
499		// TODO: More comprehensive validation using url crate
500		Ok(())
501	}
502
503	/// Validate string length
504	///
505	/// Defensive utility to validate string length bounds.
506	///
507	/// # Arguments
508	///
509	/// * `value` - The string to validate
510	/// * `min_len` - Minimum allowed length (inclusive)
511	/// * `MaxLength` - Maximum allowed length (inclusive)
512	pub fn ValidateStringLength(Value:&str, MinLen:usize, MaxLen:usize) -> Result<()> {
513		if Value.len() < MinLen {
514			return Err(AirError::Validation(format!(
515				"String too short (min: {}, got: {})",
516				MinLen,
517				Value.len()
518			)));
519		}
520
521		if Value.len() > MaxLen {
522			return Err(AirError::Validation(format!(
523				"String too long (max: {}, got: {})",
524				MaxLen,
525				Value.len()
526			)));
527		}
528
529		Ok(())
530	}
531
532	/// Validate port number
533	///
534	/// Ensures a port number is within the valid range.
535	///
536	/// # Arguments
537	///
538	/// * `port` - The port number to validate
539	///
540	/// # Errors
541	///
542	/// Returns an error if the port is not in the valid range (1-65535).
543	pub fn ValidatePort(Port:u16) -> Result<()> {
544		if Port == 0 {
545			return Err(AirError::Validation("Port cannot be 0".to_string()));
546		}
547
548		// Port 0 is valid for binding (ephemeral), but not for configuration
549		// Port 1024 and below require root/admin privileges
550		// We allow any port 1-65535 for flexibility
551		Ok(())
552	}
553
554	/// Sanitize a string for logging
555	///
556	/// Removes or escapes potentially sensitive information from strings
557	/// before logging to prevent information leakage in logs.
558	///
559	/// # Arguments
560	///
561	/// * `Value` - The string to sanitize
562	/// * `MaxLength` - Maximum length before truncation
563	///
564	/// # Returns
565	///
566	/// Sanitized string safe for logging.
567	pub fn SanitizeForLogging(Value:&str, MaxLength:usize) -> String {
568		// Truncate if too long
569		let Truncated = if Value.len() > MaxLength { &Value[..MaxLength] } else { Value };
570
571		// Remove or escape sensitive patterns
572		let Sanitized = Truncated.replace('\n', " ").replace('\r', " ").replace('\t', " ");
573
574		// If we truncated, add indicator
575		if Value.len() > MaxLength {
576			format!("{}[...]", Sanitized)
577		} else {
578			Sanitized.to_string()
579		}
580	}
581
582	/// Calculate exponential backoff delay
583	///
584	/// Implements exponential backoff with jitter for retry operations.
585	///
586	/// # Arguments
587	///
588	/// * `Attempt` - Current attempt number (0-indexed)
589	/// * `BaseDelayMs` - Base delay in milliseconds
590	/// * `MaxDelayMs` - Maximum delay in milliseconds
591	///
592	/// # Returns
593	///
594	/// Calculated delay in milliseconds with jitter applied.
595	pub fn CalculateBackoffDelay(Attempt:u32, BaseDelayMs:u64, MaxDelayMs:u64) -> u64 {
596		// Calculate exponential delay: base * 2^attempt
597		let ExponentialDelay = BaseDelayMs * 2u64.pow(Attempt);
598
599		// Cap at max delay
600		let CappedDelay = ExponentialDelay.min(MaxDelayMs);
601
602		// Add jitter (±25%)
603		use std::time::SystemTime;
604		let Seed = SystemTime::now()
605			.duration_since(SystemTime::UNIX_EPOCH)
606			.unwrap_or_default()
607			.subsec_nanos() as u64;
608
609		let JitterRange = (CappedDelay / 4).max(1); // 25% of delay, at least 1ms
610		let Jitter = (Seed % (2 * JitterRange)) as i64 - JitterRange as i64;
611
612		// Apply jitter (ensure non-negative)
613		((CappedDelay as i64) + Jitter).max(0) as u64
614	}
615
616	/// Format bytes as human-readable size
617	///
618	/// Converts a byte count to a human-readable format with appropriate units.
619	///
620	/// # Arguments
621	///
622	/// * `Bytes` - Number of bytes
623	///
624	/// # Returns
625	///
626	/// Formatted string (e.g., "1.5 MB", "256 B")
627	pub fn FormatBytes(Bytes:u64) -> String {
628		const KB:u64 = 1024;
629		const MB:u64 = KB * 1024;
630		const GB:u64 = MB * 1024;
631		const TB:u64 = GB * 1024;
632
633		if Bytes >= TB {
634			format!("{:.2} TB", Bytes as f64 / TB as f64)
635		} else if Bytes >= GB {
636			format!("{:.2} GB", Bytes as f64 / GB as f64)
637		} else if Bytes >= MB {
638			format!("{:.2} MB", Bytes as f64 / MB as f64)
639		} else if Bytes >= KB {
640			format!("{:.2} KB", Bytes as f64 / KB as f64)
641		} else {
642			format!("{} B", Bytes)
643		}
644	}
645
646	/// Parse duration string to milliseconds
647	///
648	/// Parses duration strings like "100ms", "1s", "1m", "1h" to milliseconds.
649	///
650	/// # Arguments
651	///
652	/// * `DurationStr` - Duration string (e.g., "1s", "500ms", "1m30s")
653	///
654	/// # Errors
655	///
656	/// Returns an error if the duration string is invalid.
657	///
658	/// TODO: Support complex durations like "1h30m"
659	pub fn ParseDurationToMillis(_DurationStr:&str) -> Result<u64> {
660		// TODO: Implement duration parsing with support for:
661		// - ms, s, m, h suffixes
662		// - Combined durations like "1m30s"
663		// - Decimal values like "1.5s"
664
665		Err(AirError::Internal("Duration parsing not yet implemented".to_string()))
666	}
667}