Skip to main content

zte_cpe_rs/
lib.rs

1use std::collections::HashSet;
2
3use anyhow::{Context, Result, bail};
4use bands::LteBand;
5use serde_json::Value;
6
7pub mod bands;
8#[cfg(feature = "mf289f")]
9pub(crate) mod util;
10
11#[cfg(feature = "g5ts")]
12pub mod g5ts;
13
14#[cfg(feature = "mf289f")]
15pub mod mf289f;
16
17pub(crate) fn normalize_router_url(url: &str) -> Result<String> {
18    let mut target = reqwest::Url::parse(url)
19        .with_context(|| format!("Invalid router URL: {url}"))?;
20
21    if target.cannot_be_a_base() {
22        bail!("Router URL must be an absolute base URL: {url}");
23    }
24
25    if target.query().is_some() || target.fragment().is_some() {
26        bail!("Router URL must not include a query string or fragment: {url}");
27    }
28
29    if !target.path().ends_with('/') {
30        let normalized_path = format!("{}/", target.path().trim_end_matches('/'));
31        target.set_path(&normalized_path);
32    }
33
34    Ok(target.into())
35}
36
37#[derive(serde::Serialize, Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ConnectionMode {
39    #[serde(rename = "auto_dial")]
40    Auto,
41    #[serde(rename = "manual_dial")]
42    Manual,
43}
44
45impl Default for ConnectionMode {
46    fn default() -> Self {
47        ConnectionMode::Auto
48    }
49}
50
51#[derive(serde::Serialize, Debug, Clone, Copy, PartialEq, Eq)]
52pub enum BearerPreference {
53    #[serde(rename = "NETWORK_auto")]
54    Auto,
55    /// Prefer 4G+5G automatic selection (G5TS).
56    #[serde(rename = "4G_AND_5G")]
57    LteAndNr5g,
58    /// Prefer 5G NSA mode (G5TS).
59    #[serde(rename = "LTE_AND_5G")]
60    Nr5gNsa,
61    /// Prefer 5G SA / 5G only mode (G5TS).
62    #[serde(rename = "Only_5G")]
63    OnlyNr5g,
64    #[serde(rename = "Only_LTE")]
65    OnlyLte,
66    #[serde(rename = "Only_GSM")]
67    OnlyGsm,
68    #[serde(rename = "Only_WCDMA")]
69    OnlyWcdma,
70}
71
72impl Default for BearerPreference {
73    fn default() -> Self {
74        BearerPreference::Auto
75    }
76}
77
78// --- APN types ---
79
80#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
81pub enum ApnAuthMode {
82    None,
83    Pap,
84    Chap,
85    PapChap,
86}
87
88impl std::fmt::Display for ApnAuthMode {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            ApnAuthMode::None => write!(f, "NONE"),
92            ApnAuthMode::Pap => write!(f, "PAP"),
93            ApnAuthMode::Chap => write!(f, "CHAP"),
94            ApnAuthMode::PapChap => write!(f, "PAP_CHAP"),
95        }
96    }
97}
98
99#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
100pub enum PdpType {
101    IPv4,
102    IPv6,
103    IPv4v6,
104}
105
106impl std::fmt::Display for PdpType {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            PdpType::IPv4 => write!(f, "IPv4"),
110            PdpType::IPv6 => write!(f, "IPv6"),
111            PdpType::IPv4v6 => write!(f, "IPv4v6"),
112        }
113    }
114}
115
116#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
117pub struct ApnProfile {
118    pub profile_id: Option<String>,
119    pub profile_name: String,
120    pub apn: String,
121    pub pdp_type: PdpType,
122    pub auth_mode: ApnAuthMode,
123    pub username: String,
124    pub password: String,
125}
126
127// --- DHCP types ---
128
129#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
130pub struct DhcpSettings {
131    pub ip_address: String,
132    pub subnet_mask: String,
133    pub dhcp_enabled: bool,
134    pub lease_time: u32,
135}
136
137// --- MTU types ---
138
139#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
140pub struct MtuSettings {
141    pub mtu: u32,
142    pub mss: u32,
143}
144
145// --- SMS types ---
146
147#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
148pub struct SmsSettings {
149    pub validity: String,
150    pub center_number: String,
151    pub delivery_report: bool,
152}
153
154/// Common trait implemented by all router clients.
155///
156/// Each router model provides its own implementation. Methods that are not
157/// supported by a particular model return an error.
158///
159/// ```no_run
160/// # async fn example() -> anyhow::Result<()> {
161/// use zte_cpe_rs::RouterClient;
162///
163/// // Use the concrete type for the router you have:
164/// #[cfg(feature = "gt5s")]
165/// let mut router = zte_cpe_rs::gt5s::Gt5sClient::new("https://192.168.0.1")?;
166/// #[cfg(feature = "mf289f")]
167/// let mut router = zte_cpe_rs::mf289f::Mf289fClient::new("http://192.168.0.1")?;
168///
169/// router.login("password").await?;
170/// router.disconnect_network().await?;
171/// router.logout().await?;
172/// # Ok(())
173/// # }
174/// ```
175#[async_trait::async_trait]
176pub trait RouterClient {
177    /// Authenticate with the router.
178    async fn login(&mut self, password: &str) -> Result<()>;
179
180    /// Log out from the router.
181    async fn logout(&mut self) -> Result<()>;
182
183    /// Disconnect the mobile/WAN network.
184    async fn disconnect_network(&self) -> Result<()>;
185
186    /// Connect the mobile/WAN network.
187    async fn connect_network(&self) -> Result<()>;
188
189    /// Reboot the router.
190    async fn reboot(&self) -> Result<()>;
191
192    /// Get firmware/hardware version as `(version_a, version_b)`.
193    ///
194    /// MF289F returns `(cr_version, wa_inner_version)`.
195    /// G5TS returns `(hardware_version, wa_inner_version)`.
196    async fn get_version(&self) -> Result<(String, String)>;
197
198    /// Set connection mode (auto/manual) and roaming.
199    async fn set_connection_mode(
200        &self,
201        connection_mode: ConnectionMode,
202        roam: bool,
203    ) -> Result<()>;
204
205    /// Set network bearer preference.
206    ///
207    /// Common values (`Auto`, `OnlyLte`, `OnlyGsm`, `OnlyWcdma`) work on both
208    /// families where supported by firmware.
209    /// G5TS-specific 5G values (`LteAndNr5g`, `Nr5gNsa`, `OnlyNr5g`) return an
210    /// unsupported error on MF289F.
211    async fn set_network_bearer_preference(
212        &self,
213        bearer_preference: BearerPreference,
214    ) -> Result<()>;
215
216    /// Enable or disable UPnP.
217    async fn set_upnp(&self, enabled: bool) -> Result<()>;
218
219    /// Set DMZ host IP, or disable DMZ with `None`.
220    async fn set_dmz(&self, ip_address: Option<String>) -> Result<()>;
221
222    /// Lock specific LTE bands, or unlock all with `None`.
223    async fn select_lte_band(&self, lte_band: Option<HashSet<LteBand>>) -> Result<()>;
224
225    /// Set DNS mode: `None` for auto, `Some([primary, secondary])` for manual.
226    async fn set_dns(&self, manual: Option<[String; 2]>) -> Result<()>;
227
228    /// Get router status info as JSON.
229    async fn get_status(&self) -> Result<Value>;
230
231    /// Get the current APN mode: true = manual, false = auto.
232    async fn get_apn_mode(&self) -> Result<bool> {
233        bail!("get_apn_mode is not supported on this model")
234    }
235
236    /// Set APN mode: true = manual, false = auto.
237    async fn set_apn_mode(&self, _manual: bool) -> Result<()> {
238        bail!("set_apn_mode is not supported on this model")
239    }
240
241    /// List manual APN profiles.
242    async fn get_apn_profiles(&self) -> Result<Vec<ApnProfile>> {
243        bail!("get_apn_profiles is not supported on this model")
244    }
245
246    /// Modify an existing manual APN profile.
247    async fn set_apn_profile(&self, _profile: &ApnProfile) -> Result<()> {
248        bail!("set_apn_profile is not supported on this model")
249    }
250
251    /// Set a manual APN profile as the active/default one.
252    async fn enable_apn_profile(&self, _profile_id: &str) -> Result<()> {
253        bail!("enable_apn_profile is not supported on this model")
254    }
255
256    /// Get current DHCP settings.
257    async fn get_dhcp_settings(&self) -> Result<DhcpSettings> {
258        bail!("get_dhcp_settings is not supported on this model")
259    }
260
261    /// Set DHCP settings.
262    async fn set_dhcp_settings(&self, _settings: &DhcpSettings) -> Result<()> {
263        bail!("set_dhcp_settings is not supported on this model")
264    }
265
266    /// Get current MTU/MSS settings.
267    async fn get_mtu_settings(&self) -> Result<MtuSettings> {
268        bail!("get_mtu_settings is not supported on this model")
269    }
270
271    /// Set MTU/MSS settings.
272    async fn set_mtu_settings(&self, _settings: &MtuSettings) -> Result<()> {
273        bail!("set_mtu_settings is not supported on this model")
274    }
275
276    /// Get SMS settings.
277    async fn get_sms_settings(&self) -> Result<SmsSettings> {
278        bail!("get_sms_settings is not supported on this model")
279    }
280
281    /// Get network/signal information.
282    async fn get_network_info(&self) -> Result<Value> {
283        bail!("get_network_info is not supported on this model")
284    }
285
286    /// Get SIM card information.
287    async fn get_sim_info(&self) -> Result<Value> {
288        bail!("get_sim_info is not supported on this model")
289    }
290
291    /// Get device information (IMEI, versions, etc).
292    async fn get_device_info(&self) -> Result<Value> {
293        bail!("get_device_info is not supported on this model")
294    }
295
296    /// Get connected device list.
297    async fn get_connected_devices(&self) -> Result<Value> {
298        bail!("get_connected_devices is not supported on this model")
299    }
300}
301