diff --git a/Cargo.lock b/Cargo.lock index 4749e52..adede5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,9 +196,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "http" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -337,9 +337,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.123" +version = "0.2.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" [[package]] name = "log" @@ -369,7 +369,6 @@ dependencies = [ "base64", "reqwest", "serde", - "serde_derive", "thiserror", "tokio", "tokio-test", @@ -479,9 +478,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -662,6 +661,9 @@ name = "serde" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -721,9 +723,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -766,9 +768,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -781,14 +783,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" dependencies = [ "bytes", "libc", "memchr", "mio", + "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -885,9 +888,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -911,9 +914,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 70b4dc1..f82cda4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ tokio = { version = "1.17.0", features = ["macros"] } [dependencies] base64 = "0.13.0" reqwest = { version = "0.11.4", features = ["rustls-tls", "serde_json", "json"] } -serde = "1.0.130" -serde_derive = "1.0.136" +serde = { version = "1.0.130", features = ["derive"] } thiserror = "1.0.30" url = "2.2.2" diff --git a/src/client.rs b/src/client.rs index 5cedbc5..37a183f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,6 +3,7 @@ use reqwest::{ Method, Request, Url, }; use serde::de::DeserializeOwned; +use std::num::ParseFloatError; use thiserror::Error; pub struct Client { @@ -67,4 +68,7 @@ pub enum ClientError { #[error(transparent)] ReqwestError(#[from] reqwest::Error), + + #[error(transparent)] + ParseFloatError(#[from] ParseFloatError), } diff --git a/src/interface/interface.rs b/src/interface/interface.rs deleted file mode 100644 index b51ee94..0000000 --- a/src/interface/interface.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::{interface::types::Interface, Client, ClientError}; - -pub async fn list(client: &mut Client) -> Result, ClientError> { - let url = super::BASE; - - client.execute_get::>(&url).await -} - -pub async fn get(client: &mut Client, ifid: &str) -> Result { - let url = format!("{}/{}", super::BASE, ifid); - - client.execute_get::(&url).await -} diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 05859ef..3a45f06 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -1,7 +1,17 @@ -mod interface; mod types; +pub mod wireguard; -pub use interface::*; -pub use types::*; +pub use crate::interface::types::{Interface, InterfaceType, Mtu}; +use crate::{Client, ClientError}; const BASE: &str = "rest/interface"; + +pub async fn list(client: &mut Client) -> Result, ClientError> { + client.execute_get::>(BASE).await +} + +pub async fn get(client: &mut Client, ifid: &str) -> Result { + let url = format!("{}/{}", BASE, ifid); + + client.execute_get::(&url).await +} diff --git a/src/interface/types.rs b/src/interface/types.rs index 8b82167..334131a 100644 --- a/src/interface/types.rs +++ b/src/interface/types.rs @@ -1,58 +1,143 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use crate::serde_helpers::{ + deserialize_bool, deserialize_u16, deserialize_u64, maybe_deserialize_u16, +}; +use serde::{ + de::{Error, Unexpected}, + Deserialize, Deserializer, Serialize, +}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum InterfaceType { + #[serde(rename = "vlan")] + VLAN, + #[serde(rename = "ether")] + Ethernet, + #[serde(rename = "bridge")] + Bridge, + #[serde(rename = "wg")] + Wireguard, + #[serde(rename = "pppoe-out")] + PPPoE, +} + +impl Default for InterfaceType { + fn default() -> Self { + Self::Ethernet + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum Mtu { + #[serde(rename = "auto")] + Auto, + Value(u16), +} + +impl Default for Mtu { + fn default() -> Self { + Self::Auto + } +} + +pub fn maybe_deserialize_mtu<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + if s == "auto" { + return Ok(Some(Mtu::Auto)); + } + + match s.parse::() { + Ok(v) => Ok(Some(Mtu::Value(v))), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &"an integer")), + } +} #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Interface { #[serde(rename = ".id")] pub id: String, - #[serde(rename = "actual-mtu")] - pub actual_mtu: Option, + #[serde(default, rename = "actual-mtu", deserialize_with = "deserialize_u16")] + pub actual_mtu: u16, pub comment: Option, #[serde(rename = "default-name")] pub default_name: Option, - pub disabled: String, - #[serde(rename = "fp-rx-byte")] - pub fp_rx_byte: String, - #[serde(rename = "fp-rx-packet")] - pub fp_rx_packet: String, - #[serde(rename = "fp-tx-byte")] - pub fp_tx_byte: String, - #[serde(rename = "fp-tx-packet")] - pub fp_tx_packet: String, - pub l2mtu: Option, + #[serde(deserialize_with = "deserialize_bool")] + pub disabled: bool, + #[serde(default, rename = "fp-rx-byte", deserialize_with = "deserialize_u64")] + pub fp_rx_byte: u64, + #[serde(default, rename = "fp-rx-packet", deserialize_with = "deserialize_u64")] + pub fp_rx_packet: u64, + #[serde(default, rename = "fp-tx-byte", deserialize_with = "deserialize_u64")] + pub fp_tx_byte: u64, + #[serde(default, rename = "fp-tx-packet", deserialize_with = "deserialize_u64")] + pub fp_tx_packet: u64, + #[serde(default, deserialize_with = "deserialize_u16")] + pub l2mtu: u16, #[serde(rename = "last-link-up-time")] pub last_link_up_time: Option, - #[serde(rename = "link-downs")] - pub link_downs: String, + #[serde(default, rename = "link-downs", deserialize_with = "deserialize_u16")] + pub link_downs: u16, #[serde(rename = "mac-address")] pub mac_address: Option, - #[serde(rename = "max-l2mtu")] - pub max_l2mtu: Option, - pub mtu: Option, + #[serde( + default, + rename = "max-l2mtu", + deserialize_with = "maybe_deserialize_u16" + )] + pub max_l2mtu: Option, + #[serde(default, deserialize_with = "maybe_deserialize_mtu")] + pub mtu: Option, pub name: String, - pub running: String, - #[serde(rename = "rx-byte")] - pub rx_byte: String, - #[serde(rename = "rx-drop")] - pub rx_drop: String, - #[serde(rename = "rx-error")] - pub rx_error: String, - #[serde(rename = "rx-packet")] - pub rx_packet: String, + #[serde(deserialize_with = "deserialize_bool")] + pub running: bool, + #[serde(rename = "rx-byte", deserialize_with = "deserialize_u64")] + pub rx_byte: u64, + #[serde(rename = "rx-drop", deserialize_with = "deserialize_u64")] + pub rx_drop: u64, + #[serde(rename = "rx-error", deserialize_with = "deserialize_u64")] + pub rx_error: u64, + #[serde(rename = "rx-packet", deserialize_with = "deserialize_u64")] + pub rx_packet: u64, pub slave: Option, - #[serde(rename = "tx-byte")] - pub tx_byte: String, - #[serde(rename = "tx-drop")] - pub tx_drop: String, - #[serde(rename = "tx-error")] - pub tx_error: String, - #[serde(rename = "tx-packet")] - pub tx_packet: String, - #[serde(rename = "tx-queue-drop")] - pub tx_queue_drop: String, + #[serde(rename = "tx-byte", deserialize_with = "deserialize_u64")] + pub tx_byte: u64, + #[serde(rename = "tx-drop", deserialize_with = "deserialize_u64")] + pub tx_drop: u64, + #[serde(rename = "tx-error", deserialize_with = "deserialize_u64")] + pub tx_error: u64, + #[serde(rename = "tx-packet", deserialize_with = "deserialize_u64")] + pub tx_packet: u64, + #[serde(rename = "tx-queue-drop", deserialize_with = "deserialize_u64")] + pub tx_queue_drop: u64, #[serde(rename = "type")] - pub type_field: String, + pub type_field: InterfaceType, #[serde(rename = "last-link-down-time")] pub last_link_down_time: Option, } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WireguardInterface { + #[serde(rename = ".id")] + pub id: String, + pub comment: String, + #[serde(deserialize_with = "deserialize_bool")] + pub disabled: bool, + #[serde(rename = "listen-port", deserialize_with = "deserialize_u16")] + pub listen_port: u16, + #[serde(deserialize_with = "deserialize_u16")] + pub mtu: u16, + pub name: String, + #[serde(rename = "private-key")] + pub private_key: String, + #[serde(rename = "public-key")] + pub public_key: String, + #[serde(deserialize_with = "deserialize_bool")] + pub running: bool, +} diff --git a/src/interface/wireguard.rs b/src/interface/wireguard.rs new file mode 100644 index 0000000..684e6d6 --- /dev/null +++ b/src/interface/wireguard.rs @@ -0,0 +1,15 @@ +use crate::{interface::types::WireguardInterface, Client, ClientError}; + +/// list all wireguard interfaces +pub async fn list(client: &mut Client) -> Result, ClientError> { + let url = format!("{}/wireguard", super::BASE); + + client.execute_get::>(&url).await +} + +/// get wireguard interface with the provided id +pub async fn get(client: &mut Client, wg_id: &str) -> Result { + let url = format!("{}/wireguard/{}", super::BASE, wg_id); + + client.execute_get::(&url).await +} diff --git a/src/ip/address.rs b/src/ip/address.rs new file mode 100644 index 0000000..23424de --- /dev/null +++ b/src/ip/address.rs @@ -0,0 +1,16 @@ +pub use crate::ip::types::Address; +use crate::{Client, ClientError}; + +/// list all configurd ipv4 addresses +pub async fn list(client: &mut Client) -> Result, ClientError> { + let url = format!("{}/address", super::BASE); + + client.execute_get::>(&url).await +} + +/// get details of a specific ipv4 address +pub async fn get(client: &mut Client, aid: &str) -> Result { + let url = format!("{}/address/{}", super::BASE, aid); + + client.execute_get::
(&url).await +} diff --git a/src/ip/dhcp_server.rs b/src/ip/dhcp_server.rs index aeba396..d92ee71 100644 --- a/src/ip/dhcp_server.rs +++ b/src/ip/dhcp_server.rs @@ -1,38 +1,42 @@ -use crate::{ - ip::types::{DhcpServer, Lease, Network}, - Client, ClientError, -}; +pub use crate::ip::types::{DhcpServer, Lease, Network}; +use crate::{Client, ClientError}; +/// list all dhcp servers pub async fn list(client: &mut Client) -> Result, ClientError> { let url = format!("{}/dhcp-server", super::BASE); client.execute_get::>(&url).await } +/// get details of a specific dhcp server. takes dhcp server name as input pub async fn get(client: &mut Client, dhcp_server_id: &str) -> Result { let url = format!("{}/dhcp-server/{}", super::BASE, dhcp_server_id); client.execute_get::(&url).await } +/// list all networks pub async fn list_network(client: &mut Client) -> Result, ClientError> { let url = format!("{}/dhcp-server/network", super::BASE); client.execute_get::>(&url).await } +/// get details of a specific network pub async fn get_network(client: &mut Client, nid: &str) -> Result { let url = format!("{}/dhcp-server/network/{}", super::BASE, nid); client.execute_get::(&url).await } +/// list all dhcp leases from all dhcp servers pub async fn list_leases(client: &mut Client) -> Result, ClientError> { let url = format!("{}/dhcp-server/lease", super::BASE); client.execute_get::>(&url).await } +/// get details of a specific lease pub async fn get_lease(client: &mut Client, lease_id: &str) -> Result { let url = format!("{}/dhcp-server/lease/{}", super::BASE, lease_id); diff --git a/src/ip/mod.rs b/src/ip/mod.rs index ec1a3b3..d1decf4 100644 --- a/src/ip/mod.rs +++ b/src/ip/mod.rs @@ -1,6 +1,5 @@ +pub mod address; pub mod dhcp_server; mod types; -pub use types::*; - const BASE: &str = "rest/ip"; diff --git a/src/ip/types.rs b/src/ip/types.rs index 0adb669..2b78d78 100644 --- a/src/ip/types.rs +++ b/src/ip/types.rs @@ -1,6 +1,5 @@ use crate::serde_helpers::deserialize_bool; -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -88,3 +87,22 @@ pub struct Network { #[serde(rename = "wins-server")] pub wins_server: String, } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Address { + #[serde(rename = ".id")] + pub id: String, + #[serde(rename = "actual-interface")] + pub actual_interface: String, + pub address: String, + pub comment: Option, + #[serde(deserialize_with = "deserialize_bool")] + pub disabled: bool, + #[serde(deserialize_with = "deserialize_bool")] + pub dynamic: bool, + pub interface: String, + #[serde(deserialize_with = "deserialize_bool")] + pub invalid: bool, + pub network: String, +} diff --git a/src/serde_helpers/mod.rs b/src/serde_helpers/mod.rs index 81f09d0..a1bf9f7 100644 --- a/src/serde_helpers/mod.rs +++ b/src/serde_helpers/mod.rs @@ -1,4 +1,7 @@ -use serde::{de::Error, Deserialize, Deserializer}; +use serde::{ + de::{Error, Unexpected}, + Deserialize, Deserializer, +}; pub fn deserialize_bool<'de, D>(deserializer: D) -> Result where @@ -12,3 +15,51 @@ where _ => Err(Error::unknown_variant(s, &["true", "false"])), } } + +pub fn deserialize_u16<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + match s.parse::() { + Ok(v) => Ok(v), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &"an integer")), + } +} + +pub fn deserialize_u64<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + match s.parse::() { + Ok(v) => Ok(v), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &"an integer")), + } +} + +pub fn maybe_deserialize_u16<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + match s.parse::() { + Ok(v) => Ok(Some(v)), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &"an integer ")), + } +} + +pub fn maybe_deserialize_u64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: &str = Deserialize::deserialize(deserializer)?; + + match s.parse::() { + Ok(v) => Ok(Some(v)), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &"an integer")), + } +} diff --git a/src/system/health.rs b/src/system/health.rs index fd81de5..84d1b29 100644 --- a/src/system/health.rs +++ b/src/system/health.rs @@ -1,7 +1,31 @@ -use crate::{system::types::Health, Client, ClientError}; +pub use crate::system::types::Health; +use crate::{Client, ClientError}; +/// health can be used to get all health parameters of the device pub async fn health(client: &mut Client) -> Result, ClientError> { let url = format!("{}/health", super::BASE); client.execute_get::>(&url).await } + +/// voltage can be used to get device's voltag reading +pub async fn voltage(client: &mut Client) -> Result { + let url = format!("{}/health/voltage", super::BASE); + + let response = client.execute_get::(&url).await?; + + response + .value + .parse::() + .map_err(ClientError::ParseFloatError) +} + +/// temperature can be used to get device's temperature reading +pub async fn temperature(client: &mut Client) -> Result { + let url = format!("{}/health/temperature", super::BASE); + + let mut response = client.execute_get::(&url).await?; + + response.value.push_str(&response.type_field); + Ok(response.value) +} diff --git a/src/system/types.rs b/src/system/types.rs index 0a8fd98..0a76437 100644 --- a/src/system/types.rs +++ b/src/system/types.rs @@ -1,5 +1,5 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::Deserialize; +use serde::Serialize; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/tests/interfaces_test.rs b/tests/interfaces_test.rs index 50f6066..2f35519 100644 --- a/tests/interfaces_test.rs +++ b/tests/interfaces_test.rs @@ -26,3 +26,27 @@ async fn get_interface() -> Result<(), ClientError> { Ok(()) } + +#[tokio::test] +async fn list_wireguard_interfaces() -> Result<(), ClientError> { + let base = Url::parse("https://10.0.10.1")?; + let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true) + .expect("error in creating client"); + + let _ = mikrotik::interface::wireguard::list(&mut client).await?; + + Ok(()) +} + +#[tokio::test] +async fn get_wireguard_interface() -> Result<(), ClientError> { + let base = Url::parse("https://10.0.10.1")?; + let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true) + .expect("error in creating client"); + + let response = mikrotik::interface::wireguard::list(&mut client).await?; + + let _ = mikrotik::interface::wireguard::get(&mut client, &response[0].id).await?; + + Ok(()) +} diff --git a/tests/ip_test.rs b/tests/ip_test.rs index 37b07da..b91ec20 100644 --- a/tests/ip_test.rs +++ b/tests/ip_test.rs @@ -82,3 +82,31 @@ async fn get_lease() -> Result<(), ClientError> { Ok(()) } + +#[tokio::test] +async fn list_address() -> Result<(), ClientError> { + let base = Url::parse("https://10.0.10.1")?; + let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true) + .expect("error in creating client"); + + let response = mikrotik::ip::address::list(&mut client).await?; + + println!("{:?}", response); + + Ok(()) +} + +#[tokio::test] +async fn get_address() -> Result<(), ClientError> { + let base = Url::parse("https://10.0.10.1")?; + let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true) + .expect("error in creating client"); + + let response = mikrotik::ip::address::list(&mut client).await?; + + let response = mikrotik::ip::address::get(&mut client, &response[0].id).await?; + + println!("{:?}", response); + + Ok(()) +}