ntrip_core/
config.rs

1//! Configuration types for ntrip-core.
2
3use crate::Error;
4use std::fmt;
5
6/// NTRIP protocol version.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8#[non_exhaustive]
9pub enum NtripVersion {
10    /// NTRIP v1 (HTTP/1.0, ICY 200 OK response)
11    V1,
12    /// NTRIP v2 (HTTP/1.1, chunked transfer encoding)
13    V2,
14    /// Auto-detect from server response (default)
15    #[default]
16    Auto,
17}
18
19/// Connection-related configuration.
20#[derive(Debug, Clone)]
21pub struct ConnectionConfig {
22    /// Connection timeout in seconds.
23    pub timeout_secs: u32,
24    /// Read timeout in seconds (0 = no timeout).
25    pub read_timeout_secs: u32,
26    /// Maximum reconnection attempts on disconnect/timeout (0 = disabled).
27    pub max_reconnect_attempts: u32,
28    /// Delay between reconnection attempts in milliseconds.
29    pub reconnect_delay_ms: u64,
30}
31
32impl Default for ConnectionConfig {
33    fn default() -> Self {
34        Self {
35            timeout_secs: 15,
36            read_timeout_secs: 30,
37            max_reconnect_attempts: 3,
38            reconnect_delay_ms: 1000,
39        }
40    }
41}
42
43/// Configuration for an NTRIP client connection.
44#[derive(Clone)]
45pub struct NtripConfig {
46    /// Caster hostname or IP address.
47    pub host: String,
48    /// Caster port (typically 2101).
49    pub port: u16,
50    /// Mountpoint name.
51    pub mountpoint: String,
52    /// Username for authentication.
53    pub username: Option<String>,
54    /// Password for authentication.
55    pub password: Option<String>,
56    /// Use HTTPS/TLS.
57    pub use_tls: bool,
58    /// Skip TLS certificate verification (insecure, for testing only).
59    pub tls_skip_verify: bool,
60    /// NTRIP protocol version.
61    pub ntrip_version: NtripVersion,
62    /// Connection configuration.
63    pub connection: ConnectionConfig,
64}
65
66impl fmt::Debug for NtripConfig {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.debug_struct("NtripConfig")
69            .field("host", &self.host)
70            .field("port", &self.port)
71            .field("mountpoint", &self.mountpoint)
72            .field("username", &self.username)
73            .field("password", &self.password.as_ref().map(|_| "[REDACTED]"))
74            .field("use_tls", &self.use_tls)
75            .field("tls_skip_verify", &self.tls_skip_verify)
76            .field("ntrip_version", &self.ntrip_version)
77            .field("connection", &self.connection)
78            .finish()
79    }
80}
81
82impl NtripConfig {
83    /// Create a new configuration with required fields.
84    pub fn new(host: impl Into<String>, port: u16, mountpoint: impl Into<String>) -> Self {
85        Self {
86            host: host.into(),
87            port,
88            mountpoint: mountpoint.into(),
89            username: None,
90            password: None,
91            use_tls: false,
92            tls_skip_verify: false,
93            ntrip_version: NtripVersion::Auto,
94            connection: ConnectionConfig::default(),
95        }
96    }
97
98    /// Set credentials for authentication.
99    pub fn with_credentials(
100        mut self,
101        username: impl Into<String>,
102        password: impl Into<String>,
103    ) -> Self {
104        self.username = Some(username.into());
105        self.password = Some(password.into());
106        self
107    }
108
109    /// Enable TLS/HTTPS.
110    pub fn with_tls(mut self) -> Self {
111        self.use_tls = true;
112        self
113    }
114
115    /// Skip TLS certificate verification (insecure).
116    pub fn with_tls_skip_verify(mut self) -> Self {
117        self.tls_skip_verify = true;
118        self
119    }
120
121    /// Set NTRIP protocol version.
122    pub fn with_version(mut self, version: NtripVersion) -> Self {
123        self.ntrip_version = version;
124        self
125    }
126
127    /// Set connection timeout.
128    pub fn with_timeout(mut self, timeout_secs: u32) -> Self {
129        self.connection.timeout_secs = timeout_secs;
130        self
131    }
132
133    /// Set read timeout.
134    pub fn with_read_timeout(mut self, read_timeout_secs: u32) -> Self {
135        self.connection.read_timeout_secs = read_timeout_secs;
136        self
137    }
138
139    /// Set maximum reconnection attempts (0 = disabled).
140    pub fn with_reconnect(mut self, max_attempts: u32, delay_ms: u64) -> Self {
141        self.connection.max_reconnect_attempts = max_attempts;
142        self.connection.reconnect_delay_ms = delay_ms;
143        self
144    }
145
146    /// Disable automatic reconnection.
147    pub fn without_reconnect(mut self) -> Self {
148        self.connection.max_reconnect_attempts = 0;
149        self
150    }
151
152    /// Validate the configuration.
153    pub fn validate(&self) -> Result<(), Error> {
154        if self.host.is_empty() {
155            return Err(Error::InvalidConfig {
156                message: "Host cannot be empty".to_string(),
157            });
158        }
159        if self.port == 0 {
160            return Err(Error::InvalidConfig {
161                message: "Port cannot be 0".to_string(),
162            });
163        }
164        // Validate against header injection (control characters)
165        Self::validate_no_control_chars(&self.host, "host")?;
166        Self::validate_no_control_chars(&self.mountpoint, "mountpoint")?;
167        if let Some(ref u) = self.username {
168            Self::validate_no_control_chars(u, "username")?;
169        }
170        if let Some(ref p) = self.password {
171            Self::validate_no_control_chars(p, "password")?;
172        }
173        Ok(())
174    }
175
176    /// Validate that a string contains no ASCII control characters (header injection prevention).
177    fn validate_no_control_chars(s: &str, field_name: &str) -> Result<(), Error> {
178        if s.bytes().any(|b| b < 0x20 || b == 0x7F) {
179            return Err(Error::InvalidConfig {
180                message: format!(
181                    "{} contains invalid control characters (possible header injection)",
182                    field_name
183                ),
184            });
185        }
186        Ok(())
187    }
188}