1use std::str::FromStr;
4
5use strum::{Display, EnumString, VariantNames};
6
7use crate::NtripClientError;
8
9#[derive(Clone, PartialEq, Debug)]
11#[cfg_attr(feature = "clap", derive(clap::Parser))]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct NtripConfig {
14 #[cfg_attr(
16 feature = "clap",
17 clap(long = "ntrip-host", env = "NTRIP_HOST", default_value = "rtk2go.com")
18 )]
19 pub host: String,
20
21 #[cfg_attr(
23 feature = "clap",
24 clap(long = "ntrip-port", env = "NTRIP_PORT", default_value_t = 2101)
25 )]
26 pub port: u16,
27
28 #[cfg_attr(
30 feature = "clap",
31 clap(long = "ntrip-use-tls", env = "NTRIP_USE_TLS", default_value_t = false)
32 )]
33 pub use_tls: bool,
34}
35
36impl Default for NtripConfig {
37 fn default() -> Self {
40 Self::from_provider(RtcmProvider::Centipede)
41 }
42}
43
44impl NtripConfig {
45 pub fn to_url(&self) -> String {
47 format!("{}:{}", self.host, self.port)
48 }
49
50 pub fn from_provider(network: RtcmProvider) -> Self {
52 Self {
53 host: network.host().to_string(),
54 port: network.port(),
55 use_tls: network.uses_tls(),
56 }
57 }
58
59 pub fn with_host(&self, address: &str) -> Self {
61 let mut s = self.clone();
62 s.host = address.to_string();
63 s
64 }
65
66 pub fn with_port(&self, port: u16) -> Self {
68 let mut s = self.clone();
69 s.port = port;
70 s
71 }
72
73 pub fn with_tls(&self) -> Self {
75 let mut s = self.clone();
76 s.use_tls = true;
77 s
78 }
79
80 pub fn without_tls(&self) -> Self {
82 let mut s = self.clone();
83 s.use_tls = false;
84 s
85 }
86}
87
88#[derive(Clone, Default, PartialEq)]
90#[cfg_attr(feature = "clap", derive(clap::Parser))]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub struct NtripCredentials {
93 #[cfg_attr(feature = "clap", clap(long = "ntrip-user", env = "NTRIP_USER"))]
95 pub user: String,
96
97 #[cfg_attr(
99 feature = "clap",
100 clap(long = "ntrip-pass", env = "NTRIP_PASS", default_value = "")
101 )]
102 pub pass: String,
103}
104
105impl NtripCredentials {
106 pub fn with_username(&self, username: &str) -> Self {
108 let mut s = self.clone();
109 s.user = username.to_string();
110 s
111 }
112
113 pub fn with_password(&self, password: &str) -> Self {
115 let mut s = self.clone();
116 s.pass = password.to_string();
117 s
118 }
119}
120
121impl std::fmt::Debug for NtripCredentials {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("NtripCredentials")
124 .field("user", &self.user)
125 .field("pass", &"********")
126 .finish()
127 }
128}
129
130#[derive(Clone, PartialEq, Debug, EnumString, Display, VariantNames)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133pub enum RtcmProvider {
134 #[strum(serialize = "linz")]
138 Linz,
139 #[strum(serialize = "rtk2go")]
141 Rtk2Go,
142 #[strum(serialize = "posau")]
146 PosAu,
147 #[strum(serialize = "centipede")]
149 Centipede,
150}
151
152impl RtcmProvider {
153 pub fn host(&self) -> &str {
155 match self {
156 RtcmProvider::Linz => "positionz-rt.linz.govt.nz",
157 RtcmProvider::Rtk2Go => "rtk2go.com",
158 RtcmProvider::PosAu => "ntrip.data.gnss.ga.gov.au",
159 RtcmProvider::Centipede => "caster.centipede.fr",
160 }
161 }
162
163 pub fn port(&self) -> u16 {
165 match self {
166 RtcmProvider::Linz => 2101,
167 RtcmProvider::Rtk2Go => 2101,
168 RtcmProvider::PosAu => 443,
169 RtcmProvider::Centipede => 2101,
170 }
171 }
172
173 pub fn uses_tls(&self) -> bool {
175 match self {
176 RtcmProvider::Linz => false,
177 RtcmProvider::Rtk2Go => false,
178 RtcmProvider::PosAu => true,
179 RtcmProvider::Centipede => false,
180 }
181 }
182}
183
184impl FromStr for NtripConfig {
208 type Err = NtripClientError;
209
210 fn from_str(s: &str) -> Result<Self, Self::Err> {
212 if let Ok(provider) = RtcmProvider::from_str(s) {
214 return Ok(NtripConfig::from_provider(provider));
215 }
216
217 let proto = if s.starts_with("http://") {
219 "http"
220 } else if s.starts_with("https://") {
221 "https"
222 } else if s.starts_with("ntrip://") {
223 "ntrip"
224 } else {
225 "unknown"
226 };
227 let s = s.trim_start_matches(&format!("{proto}://"));
228
229 let parts: Vec<&str> = s.split(':').collect();
231 if parts.is_empty() {
232 return Err(NtripClientError::InvalidUrl);
233 }
234 let host = parts[0].to_string();
235
236 let port = if parts.len() > 1 {
238 parts[1]
239 .parse::<u16>()
240 .map_err(|_| NtripClientError::InvalidPort)?
241 } else if proto == "https" {
242 443
243 } else {
244 2101
245 };
246 Ok(NtripConfig {
247 host,
248 port,
249 use_tls: port == 443,
250 })
251 }
252}