use std::str::FromStr;
use strum::{Display, EnumString, VariantNames};
use crate::NtripClientError;
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NtripConfig {
#[cfg_attr(
feature = "clap",
clap(long = "ntrip-host", env = "NTRIP_HOST", default_value = "rtk2go.com")
)]
pub host: String,
#[cfg_attr(
feature = "clap",
clap(long = "ntrip-port", env = "NTRIP_PORT", default_value_t = 2101)
)]
pub port: u16,
#[cfg_attr(
feature = "clap",
clap(long = "ntrip-use-tls", env = "NTRIP_USE_TLS", default_value_t = false)
)]
pub use_tls: bool,
}
impl Default for NtripConfig {
fn default() -> Self {
Self::from_provider(RtcmProvider::Centipede)
}
}
impl NtripConfig {
pub fn to_url(&self) -> String {
format!("{}:{}", self.host, self.port)
}
pub fn from_provider(network: RtcmProvider) -> Self {
Self {
host: network.host().to_string(),
port: network.port(),
use_tls: network.uses_tls(),
}
}
pub fn with_host(&self, address: &str) -> Self {
let mut s = self.clone();
s.host = address.to_string();
s
}
pub fn with_port(&self, port: u16) -> Self {
let mut s = self.clone();
s.port = port;
s
}
pub fn with_tls(&self) -> Self {
let mut s = self.clone();
s.use_tls = true;
s
}
pub fn without_tls(&self) -> Self {
let mut s = self.clone();
s.use_tls = false;
s
}
}
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NtripCredentials {
#[cfg_attr(feature = "clap", clap(long = "ntrip-user", env = "NTRIP_USER"))]
pub user: String,
#[cfg_attr(
feature = "clap",
clap(long = "ntrip-pass", env = "NTRIP_PASS", default_value = "")
)]
pub pass: String,
}
impl NtripCredentials {
pub fn with_username(&self, username: &str) -> Self {
let mut s = self.clone();
s.user = username.to_string();
s
}
pub fn with_password(&self, password: &str) -> Self {
let mut s = self.clone();
s.pass = password.to_string();
s
}
}
impl std::fmt::Debug for NtripCredentials {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NtripCredentials")
.field("user", &self.user)
.field("pass", &"********")
.finish()
}
}
#[derive(Clone, PartialEq, Debug, EnumString, Display, VariantNames)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcmProvider {
#[strum(serialize = "linz")]
Linz,
#[strum(serialize = "rtk2go")]
Rtk2Go,
#[strum(serialize = "posau")]
PosAu,
#[strum(serialize = "centipede")]
Centipede,
}
impl RtcmProvider {
pub fn host(&self) -> &str {
match self {
RtcmProvider::Linz => "positionz-rt.linz.govt.nz",
RtcmProvider::Rtk2Go => "rtk2go.com",
RtcmProvider::PosAu => "ntrip.data.gnss.ga.gov.au",
RtcmProvider::Centipede => "caster.centipede.fr",
}
}
pub fn port(&self) -> u16 {
match self {
RtcmProvider::Linz => 2101,
RtcmProvider::Rtk2Go => 2101,
RtcmProvider::PosAu => 443,
RtcmProvider::Centipede => 2101,
}
}
pub fn uses_tls(&self) -> bool {
match self {
RtcmProvider::Linz => false,
RtcmProvider::Rtk2Go => false,
RtcmProvider::PosAu => true,
RtcmProvider::Centipede => false,
}
}
}
impl FromStr for NtripConfig {
type Err = NtripClientError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(provider) = RtcmProvider::from_str(s) {
return Ok(NtripConfig::from_provider(provider));
}
let proto = if s.starts_with("http://") {
"http"
} else if s.starts_with("https://") {
"https"
} else if s.starts_with("ntrip://") {
"ntrip"
} else {
"unknown"
};
let s = s.trim_start_matches(&format!("{proto}://"));
let parts: Vec<&str> = s.split(':').collect();
if parts.is_empty() {
return Err(NtripClientError::InvalidUrl);
}
let host = parts[0].to_string();
let port = if parts.len() > 1 {
parts[1]
.parse::<u16>()
.map_err(|_| NtripClientError::InvalidPort)?
} else if proto == "https" {
443
} else {
2101
};
Ok(NtripConfig {
host,
port,
use_tls: port == 443,
})
}
}