diff options
-rw-r--r-- | .sqlx/query-1dc5f44967ffdee882f4cef32262fd643b452aacca373ee527c978e816115de6.json | 8 | ||||
-rw-r--r-- | .sqlx/query-62c84231c7e9c85dc91d71f6b4f7ee6dae2130c2109fb6f1e47e0990ec395744.json | 4 | ||||
-rw-r--r-- | .sqlx/query-adead45e1a6b02d5eabd68b8cf06394a302d288e91f5eedde65db6630021f737.json | 4 | ||||
-rw-r--r-- | Cargo.lock | 50 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | migrations/20231009123228_devices.sql | 4 | ||||
-rw-r--r-- | src/db.rs | 6 | ||||
-rw-r--r-- | src/error.rs | 22 | ||||
-rw-r--r-- | src/main.rs | 54 | ||||
-rw-r--r-- | src/routes.rs (renamed from src/routes/mod.rs) | 0 | ||||
-rw-r--r-- | src/routes/device.rs | 19 | ||||
-rw-r--r-- | src/routes/start.rs | 78 | ||||
-rw-r--r-- | src/routes/status.rs | 79 | ||||
-rw-r--r-- | src/services.rs (renamed from src/services/mod.rs) | 0 | ||||
-rw-r--r-- | src/services/ping.rs | 154 |
15 files changed, 313 insertions, 173 deletions
diff --git a/.sqlx/query-1dc5f44967ffdee882f4cef32262fd643b452aacca373ee527c978e816115de6.json b/.sqlx/query-1dc5f44967ffdee882f4cef32262fd643b452aacca373ee527c978e816115de6.json index 33d524d..dd85eaa 100644 --- a/.sqlx/query-1dc5f44967ffdee882f4cef32262fd643b452aacca373ee527c978e816115de6.json +++ b/.sqlx/query-1dc5f44967ffdee882f4cef32262fd643b452aacca373ee527c978e816115de6.json | |||
@@ -11,7 +11,7 @@ | |||
11 | { | 11 | { |
12 | "ordinal": 1, | 12 | "ordinal": 1, |
13 | "name": "mac", | 13 | "name": "mac", |
14 | "type_info": "Varchar" | 14 | "type_info": "Macaddr" |
15 | }, | 15 | }, |
16 | { | 16 | { |
17 | "ordinal": 2, | 17 | "ordinal": 2, |
@@ -21,7 +21,7 @@ | |||
21 | { | 21 | { |
22 | "ordinal": 3, | 22 | "ordinal": 3, |
23 | "name": "ip", | 23 | "name": "ip", |
24 | "type_info": "Varchar" | 24 | "type_info": "Inet" |
25 | }, | 25 | }, |
26 | { | 26 | { |
27 | "ordinal": 4, | 27 | "ordinal": 4, |
@@ -31,9 +31,9 @@ | |||
31 | ], | 31 | ], |
32 | "parameters": { | 32 | "parameters": { |
33 | "Left": [ | 33 | "Left": [ |
34 | "Macaddr", | ||
34 | "Varchar", | 35 | "Varchar", |
35 | "Varchar", | 36 | "Inet", |
36 | "Varchar", | ||
37 | "Text" | 37 | "Text" |
38 | ] | 38 | ] |
39 | }, | 39 | }, |
diff --git a/.sqlx/query-62c84231c7e9c85dc91d71f6b4f7ee6dae2130c2109fb6f1e47e0990ec395744.json b/.sqlx/query-62c84231c7e9c85dc91d71f6b4f7ee6dae2130c2109fb6f1e47e0990ec395744.json index 5ec47e3..905bb51 100644 --- a/.sqlx/query-62c84231c7e9c85dc91d71f6b4f7ee6dae2130c2109fb6f1e47e0990ec395744.json +++ b/.sqlx/query-62c84231c7e9c85dc91d71f6b4f7ee6dae2130c2109fb6f1e47e0990ec395744.json | |||
@@ -11,7 +11,7 @@ | |||
11 | { | 11 | { |
12 | "ordinal": 1, | 12 | "ordinal": 1, |
13 | "name": "mac", | 13 | "name": "mac", |
14 | "type_info": "Varchar" | 14 | "type_info": "Macaddr" |
15 | }, | 15 | }, |
16 | { | 16 | { |
17 | "ordinal": 2, | 17 | "ordinal": 2, |
@@ -21,7 +21,7 @@ | |||
21 | { | 21 | { |
22 | "ordinal": 3, | 22 | "ordinal": 3, |
23 | "name": "ip", | 23 | "name": "ip", |
24 | "type_info": "Varchar" | 24 | "type_info": "Inet" |
25 | }, | 25 | }, |
26 | { | 26 | { |
27 | "ordinal": 4, | 27 | "ordinal": 4, |
diff --git a/.sqlx/query-adead45e1a6b02d5eabd68b8cf06394a302d288e91f5eedde65db6630021f737.json b/.sqlx/query-adead45e1a6b02d5eabd68b8cf06394a302d288e91f5eedde65db6630021f737.json index bc4bdd3..d25b12e 100644 --- a/.sqlx/query-adead45e1a6b02d5eabd68b8cf06394a302d288e91f5eedde65db6630021f737.json +++ b/.sqlx/query-adead45e1a6b02d5eabd68b8cf06394a302d288e91f5eedde65db6630021f737.json | |||
@@ -6,9 +6,9 @@ | |||
6 | "parameters": { | 6 | "parameters": { |
7 | "Left": [ | 7 | "Left": [ |
8 | "Varchar", | 8 | "Varchar", |
9 | "Macaddr", | ||
9 | "Varchar", | 10 | "Varchar", |
10 | "Varchar", | 11 | "Inet" |
11 | "Varchar" | ||
12 | ] | 12 | ] |
13 | }, | 13 | }, |
14 | "nullable": [] | 14 | "nullable": [] |
@@ -71,7 +71,7 @@ version = "0.1.2" | |||
71 | source = "registry+https://github.com/rust-lang/crates.io-index" | 71 | source = "registry+https://github.com/rust-lang/crates.io-index" |
72 | checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" | 72 | checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" |
73 | dependencies = [ | 73 | dependencies = [ |
74 | "nix", | 74 | "nix 0.27.1", |
75 | "rand", | 75 | "rand", |
76 | ] | 76 | ] |
77 | 77 | ||
@@ -827,6 +827,15 @@ dependencies = [ | |||
827 | ] | 827 | ] |
828 | 828 | ||
829 | [[package]] | 829 | [[package]] |
830 | name = "ipnetwork" | ||
831 | version = "0.20.0" | ||
832 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
833 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" | ||
834 | dependencies = [ | ||
835 | "serde", | ||
836 | ] | ||
837 | |||
838 | [[package]] | ||
830 | name = "itertools" | 839 | name = "itertools" |
831 | version = "0.12.1" | 840 | version = "0.12.1" |
832 | source = "registry+https://github.com/rust-lang/crates.io-index" | 841 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -913,6 +922,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
913 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" | 922 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" |
914 | 923 | ||
915 | [[package]] | 924 | [[package]] |
925 | name = "mac_address" | ||
926 | version = "1.1.5" | ||
927 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
928 | checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" | ||
929 | dependencies = [ | ||
930 | "nix 0.23.2", | ||
931 | "serde", | ||
932 | "winapi", | ||
933 | ] | ||
934 | |||
935 | [[package]] | ||
916 | name = "matchers" | 936 | name = "matchers" |
917 | version = "0.1.0" | 937 | version = "0.1.0" |
918 | source = "registry+https://github.com/rust-lang/crates.io-index" | 938 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -944,6 +964,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
944 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" | 964 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" |
945 | 965 | ||
946 | [[package]] | 966 | [[package]] |
967 | name = "memoffset" | ||
968 | version = "0.6.5" | ||
969 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
970 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" | ||
971 | dependencies = [ | ||
972 | "autocfg", | ||
973 | ] | ||
974 | |||
975 | [[package]] | ||
947 | name = "mime" | 976 | name = "mime" |
948 | version = "0.3.17" | 977 | version = "0.3.17" |
949 | source = "registry+https://github.com/rust-lang/crates.io-index" | 978 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -977,6 +1006,19 @@ dependencies = [ | |||
977 | 1006 | ||
978 | [[package]] | 1007 | [[package]] |
979 | name = "nix" | 1008 | name = "nix" |
1009 | version = "0.23.2" | ||
1010 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1011 | checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" | ||
1012 | dependencies = [ | ||
1013 | "bitflags 1.3.2", | ||
1014 | "cc", | ||
1015 | "cfg-if", | ||
1016 | "libc", | ||
1017 | "memoffset", | ||
1018 | ] | ||
1019 | |||
1020 | [[package]] | ||
1021 | name = "nix" | ||
980 | version = "0.27.1" | 1022 | version = "0.27.1" |
981 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" |
982 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" | 1024 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" |
@@ -1709,7 +1751,9 @@ dependencies = [ | |||
1709 | "hashlink", | 1751 | "hashlink", |
1710 | "hex", | 1752 | "hex", |
1711 | "indexmap", | 1753 | "indexmap", |
1754 | "ipnetwork", | ||
1712 | "log", | 1755 | "log", |
1756 | "mac_address", | ||
1713 | "memchr", | 1757 | "memchr", |
1714 | "once_cell", | 1758 | "once_cell", |
1715 | "paste", | 1759 | "paste", |
@@ -1829,8 +1873,10 @@ dependencies = [ | |||
1829 | "hkdf", | 1873 | "hkdf", |
1830 | "hmac", | 1874 | "hmac", |
1831 | "home", | 1875 | "home", |
1876 | "ipnetwork", | ||
1832 | "itoa", | 1877 | "itoa", |
1833 | "log", | 1878 | "log", |
1879 | "mac_address", | ||
1834 | "md-5", | 1880 | "md-5", |
1835 | "memchr", | 1881 | "memchr", |
1836 | "once_cell", | 1882 | "once_cell", |
@@ -2360,6 +2406,8 @@ dependencies = [ | |||
2360 | "color-eyre", | 2406 | "color-eyre", |
2361 | "config", | 2407 | "config", |
2362 | "dashmap", | 2408 | "dashmap", |
2409 | "ipnetwork", | ||
2410 | "mac_address", | ||
2363 | "serde", | 2411 | "serde", |
2364 | "serde_json", | 2412 | "serde_json", |
2365 | "sqlx", | 2413 | "sqlx", |
@@ -14,10 +14,12 @@ time = { version = "0.3", features = ["macros"] } | |||
14 | serde = { version = "1.0", features = ["derive"] } | 14 | serde = { version = "1.0", features = ["derive"] } |
15 | serde_json = "1.0" | 15 | serde_json = "1.0" |
16 | config = "0.14" | 16 | config = "0.14" |
17 | sqlx = { version = "0.7", features = ["postgres", "runtime-tokio"]} | 17 | sqlx = { version = "0.7", features = ["postgres", "runtime-tokio", "ipnetwork", "mac_address"]} |
18 | surge-ping = "0.8" | 18 | surge-ping = "0.8" |
19 | axum-macros = "0.4" | 19 | axum-macros = "0.4" |
20 | uuid = { version = "1.6", features = ["v4", "fast-rng"] } | 20 | uuid = { version = "1.6", features = ["v4", "fast-rng"] } |
21 | dashmap = "5.5" | 21 | dashmap = "5.5" |
22 | color-eyre = "0.6" | 22 | color-eyre = "0.6" |
23 | thiserror = "1.0" | 23 | thiserror = "1.0" |
24 | ipnetwork = "0.20.0" | ||
25 | mac_address = { version = "1.1.5", features = ["serde"] } | ||
diff --git a/migrations/20231009123228_devices.sql b/migrations/20231009123228_devices.sql index d36946c..6983ada 100644 --- a/migrations/20231009123228_devices.sql +++ b/migrations/20231009123228_devices.sql | |||
@@ -2,8 +2,8 @@ | |||
2 | CREATE TABLE IF NOT EXISTS "devices" | 2 | CREATE TABLE IF NOT EXISTS "devices" |
3 | ( | 3 | ( |
4 | "id" VARCHAR(255) PRIMARY KEY NOT NULL, | 4 | "id" VARCHAR(255) PRIMARY KEY NOT NULL, |
5 | "mac" VARCHAR(17) NOT NULL, | 5 | "mac" MACADDR NOT NULL, |
6 | "broadcast_addr" VARCHAR(39) NOT NULL, | 6 | "broadcast_addr" VARCHAR(39) NOT NULL, |
7 | "ip" VARCHAR(39) NOT NULL, | 7 | "ip" INET NOT NULL, |
8 | "times" BIGINT[] | 8 | "times" BIGINT[] |
9 | ) | 9 | ) |
@@ -1,13 +1,13 @@ | |||
1 | use serde::Serialize; | 1 | use serde::Serialize; |
2 | use sqlx::{PgPool, postgres::PgPoolOptions}; | 2 | use sqlx::{PgPool, postgres::PgPoolOptions, types::{ipnetwork::IpNetwork, mac_address::MacAddress}}; |
3 | use tracing::{debug, info}; | 3 | use tracing::{debug, info}; |
4 | 4 | ||
5 | #[derive(Serialize, Debug)] | 5 | #[derive(Serialize, Debug)] |
6 | pub struct Device { | 6 | pub struct Device { |
7 | pub id: String, | 7 | pub id: String, |
8 | pub mac: String, | 8 | pub mac: MacAddress, |
9 | pub broadcast_addr: String, | 9 | pub broadcast_addr: String, |
10 | pub ip: String, | 10 | pub ip: IpNetwork, |
11 | pub times: Option<Vec<i64>> | 11 | pub times: Option<Vec<i64>> |
12 | } | 12 | } |
13 | 13 | ||
diff --git a/src/error.rs b/src/error.rs index 63b214e..66a61f4 100644 --- a/src/error.rs +++ b/src/error.rs | |||
@@ -2,6 +2,8 @@ use axum::http::header::ToStrError; | |||
2 | use axum::http::StatusCode; | 2 | use axum::http::StatusCode; |
3 | use axum::response::{IntoResponse, Response}; | 3 | use axum::response::{IntoResponse, Response}; |
4 | use axum::Json; | 4 | use axum::Json; |
5 | use ::ipnetwork::IpNetworkError; | ||
6 | use mac_address::MacParseError; | ||
5 | use serde_json::json; | 7 | use serde_json::json; |
6 | use std::io; | 8 | use std::io; |
7 | use tracing::error; | 9 | use tracing::error; |
@@ -29,6 +31,18 @@ pub enum Error { | |||
29 | source: ToStrError, | 31 | source: ToStrError, |
30 | }, | 32 | }, |
31 | 33 | ||
34 | #[error("string parse: {source}")] | ||
35 | IpParse { | ||
36 | #[from] | ||
37 | source: IpNetworkError, | ||
38 | }, | ||
39 | |||
40 | #[error("mac parse: {source}")] | ||
41 | MacParse { | ||
42 | #[from] | ||
43 | source: MacParseError, | ||
44 | }, | ||
45 | |||
32 | #[error("io: {source}")] | 46 | #[error("io: {source}")] |
33 | Io { | 47 | Io { |
34 | #[from] | 48 | #[from] |
@@ -57,6 +71,14 @@ impl IntoResponse for Error { | |||
57 | error!("{source}"); | 71 | error!("{source}"); |
58 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | 72 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") |
59 | } | 73 | } |
74 | Self::MacParse { source } => { | ||
75 | error!("{source}"); | ||
76 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | ||
77 | } | ||
78 | Self::IpParse { source } => { | ||
79 | error!("{source}"); | ||
80 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | ||
81 | } | ||
60 | }; | 82 | }; |
61 | let body = Json(json!({ | 83 | let body = Json(json!({ |
62 | "error": error_message, | 84 | "error": error_message, |
diff --git a/src/main.rs b/src/main.rs index 4ef129b..7d8c1da 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -1,42 +1,44 @@ | |||
1 | use std::env; | ||
2 | use std::sync::Arc; | ||
3 | use axum::{Router, routing::post}; | ||
4 | use axum::routing::{get, put}; | ||
5 | use dashmap::DashMap; | ||
6 | use sqlx::PgPool; | ||
7 | use time::util::local_offset; | ||
8 | use tokio::sync::broadcast::{channel, Sender}; | ||
9 | use tracing::{info, level_filters::LevelFilter}; | ||
10 | use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*}; | ||
11 | use crate::config::Config; | 1 | use crate::config::Config; |
12 | use crate::db::init_db_pool; | 2 | use crate::db::init_db_pool; |
13 | use crate::routes::device; | 3 | use crate::routes::device; |
14 | use crate::routes::start::start; | 4 | use crate::routes::start::start; |
15 | use crate::routes::status::status; | 5 | use crate::routes::status::status; |
16 | use crate::services::ping::{BroadcastCommands, StatusMap}; | 6 | use crate::services::ping::StatusMap; |
7 | use axum::routing::{get, put}; | ||
8 | use axum::{routing::post, Router}; | ||
9 | use dashmap::DashMap; | ||
10 | use services::ping::BroadcastCommand; | ||
11 | use sqlx::PgPool; | ||
12 | use tracing_subscriber::fmt::time::UtcTime; | ||
13 | use std::env; | ||
14 | use std::sync::Arc; | ||
15 | use tokio::sync::broadcast::{channel, Sender}; | ||
16 | use tracing::{info, level_filters::LevelFilter}; | ||
17 | use tracing_subscriber::{ | ||
18 | fmt, | ||
19 | prelude::*, | ||
20 | EnvFilter, | ||
21 | }; | ||
17 | 22 | ||
18 | mod auth; | 23 | mod auth; |
19 | mod config; | 24 | mod config; |
20 | mod routes; | ||
21 | mod wol; | ||
22 | mod db; | 25 | mod db; |
23 | mod error; | 26 | mod error; |
27 | mod routes; | ||
24 | mod services; | 28 | mod services; |
29 | mod wol; | ||
25 | 30 | ||
26 | #[tokio::main] | 31 | #[tokio::main] |
27 | async fn main() -> color_eyre::eyre::Result<()> { | 32 | async fn main() -> color_eyre::eyre::Result<()> { |
28 | |||
29 | color_eyre::install()?; | 33 | color_eyre::install()?; |
34 | |||
30 | 35 | ||
31 | unsafe { local_offset::set_soundness(local_offset::Soundness::Unsound); } | ||
32 | let time_format = | 36 | let time_format = |
33 | time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); | 37 | time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); |
34 | let loc = LocalTime::new(time_format); | 38 | let loc = UtcTime::new(time_format); |
35 | 39 | ||
36 | tracing_subscriber::registry() | 40 | tracing_subscriber::registry() |
37 | .with(fmt::layer() | 41 | .with(fmt::layer().with_timer(loc)) |
38 | .with_timer(loc) | ||
39 | ) | ||
40 | .with( | 42 | .with( |
41 | EnvFilter::builder() | 43 | EnvFilter::builder() |
42 | .with_default_directive(LevelFilter::INFO.into()) | 44 | .with_default_directive(LevelFilter::INFO.into()) |
@@ -56,8 +58,13 @@ async fn main() -> color_eyre::eyre::Result<()> { | |||
56 | let (tx, _) = channel(32); | 58 | let (tx, _) = channel(32); |
57 | 59 | ||
58 | let ping_map: StatusMap = DashMap::new(); | 60 | let ping_map: StatusMap = DashMap::new(); |
59 | 61 | ||
60 | let shared_state = Arc::new(AppState { db, config: config.clone(), ping_send: tx, ping_map }); | 62 | let shared_state = Arc::new(AppState { |
63 | db, | ||
64 | config: config.clone(), | ||
65 | ping_send: tx, | ||
66 | ping_map, | ||
67 | }); | ||
61 | 68 | ||
62 | let app = Router::new() | 69 | let app = Router::new() |
63 | .route("/start", post(start)) | 70 | .route("/start", post(start)) |
@@ -69,8 +76,7 @@ async fn main() -> color_eyre::eyre::Result<()> { | |||
69 | 76 | ||
70 | let addr = config.serveraddr; | 77 | let addr = config.serveraddr; |
71 | info!("start server on {}", addr); | 78 | info!("start server on {}", addr); |
72 | let listener = tokio::net::TcpListener::bind(addr) | 79 | let listener = tokio::net::TcpListener::bind(addr).await?; |
73 | .await?; | ||
74 | axum::serve(listener, app).await?; | 80 | axum::serve(listener, app).await?; |
75 | 81 | ||
76 | Ok(()) | 82 | Ok(()) |
@@ -79,6 +85,6 @@ async fn main() -> color_eyre::eyre::Result<()> { | |||
79 | pub struct AppState { | 85 | pub struct AppState { |
80 | db: PgPool, | 86 | db: PgPool, |
81 | config: Config, | 87 | config: Config, |
82 | ping_send: Sender<BroadcastCommands>, | 88 | ping_send: Sender<BroadcastCommand>, |
83 | ping_map: StatusMap, | 89 | ping_map: StatusMap, |
84 | } | 90 | } |
diff --git a/src/routes/mod.rs b/src/routes.rs index d5ab0d6..d5ab0d6 100644 --- a/src/routes/mod.rs +++ b/src/routes.rs | |||
diff --git a/src/routes/device.rs b/src/routes/device.rs index 5ca574a..2f0093d 100644 --- a/src/routes/device.rs +++ b/src/routes/device.rs | |||
@@ -4,9 +4,11 @@ use crate::error::Error; | |||
4 | use axum::extract::State; | 4 | use axum::extract::State; |
5 | use axum::http::HeaderMap; | 5 | use axum::http::HeaderMap; |
6 | use axum::Json; | 6 | use axum::Json; |
7 | use mac_address::MacAddress; | ||
7 | use serde::{Deserialize, Serialize}; | 8 | use serde::{Deserialize, Serialize}; |
8 | use serde_json::{json, Value}; | 9 | use serde_json::{json, Value}; |
9 | use std::sync::Arc; | 10 | use sqlx::types::ipnetwork::IpNetwork; |
11 | use std::{sync::Arc, str::FromStr}; | ||
10 | use tracing::{debug, info}; | 12 | use tracing::{debug, info}; |
11 | 13 | ||
12 | pub async fn get( | 14 | pub async fn get( |
@@ -14,7 +16,7 @@ pub async fn get( | |||
14 | headers: HeaderMap, | 16 | headers: HeaderMap, |
15 | Json(payload): Json<GetDevicePayload>, | 17 | Json(payload): Json<GetDevicePayload>, |
16 | ) -> Result<Json<Value>, Error> { | 18 | ) -> Result<Json<Value>, Error> { |
17 | info!("add device {}", payload.id); | 19 | info!("get device {}", payload.id); |
18 | let secret = headers.get("authorization"); | 20 | let secret = headers.get("authorization"); |
19 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); | 21 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); |
20 | if authorized { | 22 | if authorized { |
@@ -52,18 +54,21 @@ pub async fn put( | |||
52 | "add device {} ({}, {}, {})", | 54 | "add device {} ({}, {}, {})", |
53 | payload.id, payload.mac, payload.broadcast_addr, payload.ip | 55 | payload.id, payload.mac, payload.broadcast_addr, payload.ip |
54 | ); | 56 | ); |
57 | |||
55 | let secret = headers.get("authorization"); | 58 | let secret = headers.get("authorization"); |
56 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); | 59 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); |
57 | if authorized { | 60 | if authorized { |
61 | let ip = IpNetwork::from_str(&payload.ip)?; | ||
62 | let mac = MacAddress::from_str(&payload.mac)?; | ||
58 | sqlx::query!( | 63 | sqlx::query!( |
59 | r#" | 64 | r#" |
60 | INSERT INTO devices (id, mac, broadcast_addr, ip) | 65 | INSERT INTO devices (id, mac, broadcast_addr, ip) |
61 | VALUES ($1, $2, $3, $4); | 66 | VALUES ($1, $2, $3, $4); |
62 | "#, | 67 | "#, |
63 | payload.id, | 68 | payload.id, |
64 | payload.mac, | 69 | mac, |
65 | payload.broadcast_addr, | 70 | payload.broadcast_addr, |
66 | payload.ip | 71 | ip |
67 | ) | 72 | ) |
68 | .execute(&state.db) | 73 | .execute(&state.db) |
69 | .await?; | 74 | .await?; |
@@ -99,6 +104,8 @@ pub async fn post( | |||
99 | let secret = headers.get("authorization"); | 104 | let secret = headers.get("authorization"); |
100 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); | 105 | let authorized = matches!(auth(&state.config, secret)?, crate::auth::Response::Success); |
101 | if authorized { | 106 | if authorized { |
107 | let ip = IpNetwork::from_str(&payload.ip)?; | ||
108 | let mac = MacAddress::from_str(&payload.mac)?; | ||
102 | let device = sqlx::query_as!( | 109 | let device = sqlx::query_as!( |
103 | Device, | 110 | Device, |
104 | r#" | 111 | r#" |
@@ -106,9 +113,9 @@ pub async fn post( | |||
106 | SET mac = $1, broadcast_addr = $2, ip = $3 WHERE id = $4 | 113 | SET mac = $1, broadcast_addr = $2, ip = $3 WHERE id = $4 |
107 | RETURNING id, mac, broadcast_addr, ip, times; | 114 | RETURNING id, mac, broadcast_addr, ip, times; |
108 | "#, | 115 | "#, |
109 | payload.mac, | 116 | mac, |
110 | payload.broadcast_addr, | 117 | payload.broadcast_addr, |
111 | payload.ip, | 118 | ip, |
112 | payload.id | 119 | payload.id |
113 | ) | 120 | ) |
114 | .fetch_one(&state.db) | 121 | .fetch_one(&state.db) |
diff --git a/src/routes/start.rs b/src/routes/start.rs index ec4f98f..4888325 100644 --- a/src/routes/start.rs +++ b/src/routes/start.rs | |||
@@ -12,7 +12,6 @@ use std::sync::Arc; | |||
12 | use tracing::{debug, info}; | 12 | use tracing::{debug, info}; |
13 | use uuid::Uuid; | 13 | use uuid::Uuid; |
14 | 14 | ||
15 | #[axum_macros::debug_handler] | ||
16 | pub async fn start( | 15 | pub async fn start( |
17 | State(state): State<Arc<crate::AppState>>, | 16 | State(state): State<Arc<crate::AppState>>, |
18 | headers: HeaderMap, | 17 | headers: HeaderMap, |
@@ -41,45 +40,11 @@ pub async fn start( | |||
41 | let _ = send_packet( | 40 | let _ = send_packet( |
42 | bind_addr, | 41 | bind_addr, |
43 | &device.broadcast_addr, | 42 | &device.broadcast_addr, |
44 | &create_buffer(&device.mac)?, | 43 | &create_buffer(&device.mac.to_string())?, |
45 | )?; | 44 | )?; |
46 | let dev_id = device.id.clone(); | 45 | let dev_id = device.id.clone(); |
47 | let uuid = if payload.ping.is_some_and(|ping| ping) { | 46 | let uuid = if payload.ping.is_some_and(|ping| ping) { |
48 | let mut uuid: Option<String> = None; | 47 | Some(setup_ping(state, device)) |
49 | for (key, value) in state.ping_map.clone() { | ||
50 | if value.ip == device.ip { | ||
51 | debug!("service already exists"); | ||
52 | uuid = Some(key); | ||
53 | break; | ||
54 | } | ||
55 | } | ||
56 | let uuid_gen = match uuid { | ||
57 | Some(u) => u, | ||
58 | None => Uuid::new_v4().to_string(), | ||
59 | }; | ||
60 | let uuid_genc = uuid_gen.clone(); | ||
61 | |||
62 | tokio::spawn(async move { | ||
63 | debug!("init ping service"); | ||
64 | state.ping_map.insert( | ||
65 | uuid_gen.clone(), | ||
66 | PingValue { | ||
67 | ip: device.ip.clone(), | ||
68 | online: false, | ||
69 | }, | ||
70 | ); | ||
71 | |||
72 | crate::services::ping::spawn( | ||
73 | state.ping_send.clone(), | ||
74 | &state.config, | ||
75 | device, | ||
76 | uuid_gen.clone(), | ||
77 | &state.ping_map, | ||
78 | &state.db, | ||
79 | ) | ||
80 | .await; | ||
81 | }); | ||
82 | Some(uuid_genc) | ||
83 | } else { | 48 | } else { |
84 | None | 49 | None |
85 | }; | 50 | }; |
@@ -93,6 +58,45 @@ pub async fn start( | |||
93 | } | 58 | } |
94 | } | 59 | } |
95 | 60 | ||
61 | fn setup_ping(state: Arc<crate::AppState>, device: Device) -> String { | ||
62 | let mut uuid: Option<String> = None; | ||
63 | for (key, value) in state.ping_map.clone() { | ||
64 | if value.ip == device.ip { | ||
65 | debug!("service already exists"); | ||
66 | uuid = Some(key); | ||
67 | break; | ||
68 | } | ||
69 | } | ||
70 | let uuid_gen = match uuid { | ||
71 | Some(u) => u, | ||
72 | None => Uuid::new_v4().to_string(), | ||
73 | }; | ||
74 | let uuid_ret = uuid_gen.clone(); | ||
75 | |||
76 | debug!("init ping service"); | ||
77 | state.ping_map.insert( | ||
78 | uuid_gen.clone(), | ||
79 | PingValue { | ||
80 | ip: device.ip, | ||
81 | online: false, | ||
82 | }, | ||
83 | ); | ||
84 | |||
85 | tokio::spawn(async move { | ||
86 | crate::services::ping::spawn( | ||
87 | state.ping_send.clone(), | ||
88 | &state.config, | ||
89 | device, | ||
90 | uuid_gen, | ||
91 | &state.ping_map, | ||
92 | &state.db, | ||
93 | ) | ||
94 | .await; | ||
95 | }); | ||
96 | |||
97 | uuid_ret | ||
98 | } | ||
99 | |||
96 | #[derive(Deserialize)] | 100 | #[derive(Deserialize)] |
97 | pub struct Payload { | 101 | pub struct Payload { |
98 | id: String, | 102 | id: String, |
diff --git a/src/routes/status.rs b/src/routes/status.rs index 31ef996..0e25f7d 100644 --- a/src/routes/status.rs +++ b/src/routes/status.rs | |||
@@ -1,10 +1,79 @@ | |||
1 | use std::sync::Arc; | 1 | use crate::services::ping::BroadcastCommand; |
2 | use crate::AppState; | ||
3 | use axum::extract::ws::{Message, WebSocket}; | ||
2 | use axum::extract::{State, WebSocketUpgrade}; | 4 | use axum::extract::{State, WebSocketUpgrade}; |
3 | use axum::response::Response; | 5 | use axum::response::Response; |
4 | use crate::AppState; | 6 | use sqlx::PgPool; |
5 | use crate::services::ping::status_websocket; | 7 | use std::sync::Arc; |
8 | use tracing::{debug, trace}; | ||
6 | 9 | ||
7 | #[axum_macros::debug_handler] | ||
8 | pub async fn status(State(state): State<Arc<AppState>>, ws: WebSocketUpgrade) -> Response { | 10 | pub async fn status(State(state): State<Arc<AppState>>, ws: WebSocketUpgrade) -> Response { |
9 | ws.on_upgrade(move |socket| status_websocket(socket, state)) | 11 | ws.on_upgrade(move |socket| websocket(socket, state)) |
12 | } | ||
13 | |||
14 | pub async fn websocket(mut socket: WebSocket, state: Arc<AppState>) { | ||
15 | trace!("wait for ws message (uuid)"); | ||
16 | let msg = socket.recv().await; | ||
17 | let uuid = msg.unwrap().unwrap().into_text().unwrap(); | ||
18 | |||
19 | trace!("Search for uuid: {}", uuid); | ||
20 | |||
21 | let eta = get_eta(&state.db).await; | ||
22 | let _ = socket | ||
23 | .send(Message::Text(format!("eta_{eta}_{uuid}"))) | ||
24 | .await; | ||
25 | |||
26 | let device_exists = state.ping_map.contains_key(&uuid); | ||
27 | if device_exists { | ||
28 | let _ = socket | ||
29 | .send(receive_ping_broadcast(state.clone(), uuid).await) | ||
30 | .await; | ||
31 | } else { | ||
32 | debug!("didn't find any device"); | ||
33 | let _ = socket.send(Message::Text(format!("notfound_{uuid}"))).await; | ||
34 | }; | ||
35 | |||
36 | let _ = socket.close().await; | ||
37 | } | ||
38 | |||
39 | async fn receive_ping_broadcast(state: Arc<AppState>, uuid: String) -> Message { | ||
40 | let pm = state.ping_map.clone().into_read_only(); | ||
41 | let device = pm.get(&uuid).expect("fatal error"); | ||
42 | debug!("got device: {} (online: {})", device.ip, device.online); | ||
43 | if device.online { | ||
44 | debug!("already started"); | ||
45 | Message::Text(BroadcastCommand::success(uuid).to_string()) | ||
46 | } else { | ||
47 | loop { | ||
48 | trace!("wait for tx message"); | ||
49 | let message = state | ||
50 | .ping_send | ||
51 | .subscribe() | ||
52 | .recv() | ||
53 | .await | ||
54 | .expect("fatal error"); | ||
55 | trace!("got message {:?}", message); | ||
56 | |||
57 | if message.uuid != uuid { | ||
58 | continue; | ||
59 | } | ||
60 | trace!("message == uuid success"); | ||
61 | return Message::Text(message.to_string()); | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | async fn get_eta(db: &PgPool) -> i64 { | ||
67 | let query = sqlx::query!(r#"SELECT times FROM devices;"#) | ||
68 | .fetch_one(db) | ||
69 | .await | ||
70 | .unwrap(); | ||
71 | |||
72 | let times = if let Some(times) = query.times { | ||
73 | times | ||
74 | } else { | ||
75 | vec![0] | ||
76 | }; | ||
77 | |||
78 | times.iter().sum::<i64>() / i64::try_from(times.len()).unwrap() | ||
10 | } | 79 | } |
diff --git a/src/services/mod.rs b/src/services.rs index a766209..a766209 100644 --- a/src/services/mod.rs +++ b/src/services.rs | |||
diff --git a/src/services/ping.rs b/src/services/ping.rs index 9b164c8..9191f86 100644 --- a/src/services/ping.rs +++ b/src/services/ping.rs | |||
@@ -1,59 +1,58 @@ | |||
1 | use std::str::FromStr; | 1 | use crate::config::Config; |
2 | use std::net::IpAddr; | 2 | use crate::db::Device; |
3 | use std::sync::Arc; | ||
4 | |||
5 | use axum::extract::ws::WebSocket; | ||
6 | use axum::extract::ws::Message; | ||
7 | use dashmap::DashMap; | 3 | use dashmap::DashMap; |
4 | use ipnetwork::IpNetwork; | ||
8 | use sqlx::PgPool; | 5 | use sqlx::PgPool; |
6 | use std::fmt::Display; | ||
9 | use time::{Duration, Instant}; | 7 | use time::{Duration, Instant}; |
10 | use tokio::sync::broadcast::Sender; | 8 | use tokio::sync::broadcast::Sender; |
11 | use tracing::{debug, error, trace}; | 9 | use tracing::{debug, error, trace}; |
12 | use crate::AppState; | ||
13 | use crate::config::Config; | ||
14 | use crate::db::Device; | ||
15 | 10 | ||
16 | pub type StatusMap = DashMap<String, Value>; | 11 | pub type StatusMap = DashMap<String, Value>; |
17 | 12 | ||
18 | #[derive(Debug, Clone)] | 13 | #[derive(Debug, Clone)] |
19 | pub struct Value { | 14 | pub struct Value { |
20 | pub ip: String, | 15 | pub ip: IpNetwork, |
21 | pub online: bool | 16 | pub online: bool, |
22 | } | 17 | } |
23 | 18 | ||
24 | pub async fn spawn(tx: Sender<BroadcastCommands>, config: &Config, device: Device, uuid: String, ping_map: &StatusMap, db: &PgPool) { | 19 | pub async fn spawn( |
20 | tx: Sender<BroadcastCommand>, | ||
21 | config: &Config, | ||
22 | device: Device, | ||
23 | uuid: String, | ||
24 | ping_map: &StatusMap, | ||
25 | db: &PgPool, | ||
26 | ) { | ||
25 | let timer = Instant::now(); | 27 | let timer = Instant::now(); |
26 | let payload = [0; 8]; | 28 | let payload = [0; 8]; |
27 | 29 | ||
28 | let ping_ip = IpAddr::from_str(&device.ip).expect("bad ip"); | 30 | let mut msg: Option<BroadcastCommand> = None; |
29 | |||
30 | let mut msg: Option<BroadcastCommands> = None; | ||
31 | while msg.is_none() { | 31 | while msg.is_none() { |
32 | let ping = surge_ping::ping( | 32 | let ping = surge_ping::ping(device.ip.ip(), &payload).await; |
33 | ping_ip, | ||
34 | &payload | ||
35 | ).await; | ||
36 | 33 | ||
37 | if let Err(ping) = ping { | 34 | if let Err(ping) = ping { |
38 | let ping_timeout = matches!(ping, surge_ping::SurgeError::Timeout { .. }); | 35 | let ping_timeout = matches!(ping, surge_ping::SurgeError::Timeout { .. }); |
39 | if !ping_timeout { | 36 | if !ping_timeout { |
40 | error!("{}", ping.to_string()); | 37 | error!("{}", ping.to_string()); |
41 | msg = Some(BroadcastCommands::Error(uuid.clone())); | 38 | msg = Some(BroadcastCommand::error(uuid.clone())); |
42 | } | 39 | } |
43 | if timer.elapsed() >= Duration::minutes(config.pingtimeout) { | 40 | if timer.elapsed() >= Duration::minutes(config.pingtimeout) { |
44 | msg = Some(BroadcastCommands::Timeout(uuid.clone())); | 41 | msg = Some(BroadcastCommand::timeout(uuid.clone())); |
45 | } | 42 | } |
46 | } else { | 43 | } else { |
47 | let (_, duration) = ping.map_err(|err| error!("{}", err.to_string())).expect("fatal error"); | 44 | let (_, duration) = ping |
45 | .map_err(|err| error!("{}", err.to_string())) | ||
46 | .expect("fatal error"); | ||
48 | debug!("ping took {:?}", duration); | 47 | debug!("ping took {:?}", duration); |
49 | msg = Some(BroadcastCommands::Success(uuid.clone())); | 48 | msg = Some(BroadcastCommand::success(uuid.clone())); |
50 | }; | 49 | }; |
51 | } | 50 | } |
52 | 51 | ||
53 | let msg = msg.expect("fatal error"); | 52 | let msg = msg.expect("fatal error"); |
54 | 53 | ||
55 | let _ = tx.send(msg.clone()); | 54 | let _ = tx.send(msg.clone()); |
56 | if let BroadcastCommands::Success(..) = msg { | 55 | if let BroadcastCommands::Success = msg.command { |
57 | sqlx::query!( | 56 | sqlx::query!( |
58 | r#" | 57 | r#" |
59 | UPDATE devices | 58 | UPDATE devices |
@@ -62,8 +61,17 @@ pub async fn spawn(tx: Sender<BroadcastCommands>, config: &Config, device: Devic | |||
62 | "#, | 61 | "#, |
63 | timer.elapsed().whole_seconds(), | 62 | timer.elapsed().whole_seconds(), |
64 | device.id | 63 | device.id |
65 | ).execute(db).await.unwrap(); | 64 | ) |
66 | ping_map.insert(uuid.clone(), Value { ip: device.ip.clone(), online: true }); | 65 | .execute(db) |
66 | .await | ||
67 | .unwrap(); | ||
68 | ping_map.insert( | ||
69 | uuid.clone(), | ||
70 | Value { | ||
71 | ip: device.ip, | ||
72 | online: true, | ||
73 | }, | ||
74 | ); | ||
67 | tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; | 75 | tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; |
68 | } | 76 | } |
69 | trace!("remove {} from ping_map", uuid); | 77 | trace!("remove {} from ping_map", uuid); |
@@ -72,74 +80,48 @@ pub async fn spawn(tx: Sender<BroadcastCommands>, config: &Config, device: Devic | |||
72 | 80 | ||
73 | #[derive(Clone, Debug, PartialEq)] | 81 | #[derive(Clone, Debug, PartialEq)] |
74 | pub enum BroadcastCommands { | 82 | pub enum BroadcastCommands { |
75 | Success(String), | 83 | Success, |
76 | Timeout(String), | 84 | Timeout, |
77 | Error(String), | 85 | Error, |
78 | } | 86 | } |
79 | 87 | ||
80 | pub async fn status_websocket(mut socket: WebSocket, state: Arc<AppState>) { | 88 | #[derive(Clone, Debug, PartialEq)] |
81 | trace!("wait for ws message (uuid)"); | 89 | pub struct BroadcastCommand { |
82 | let msg = socket.recv().await; | 90 | pub uuid: String, |
83 | let uuid = msg.unwrap().unwrap().into_text().unwrap(); | 91 | pub command: BroadcastCommands, |
84 | 92 | } | |
85 | trace!("Search for uuid: {}", uuid); | ||
86 | |||
87 | let eta = get_eta(&state.db).await; | ||
88 | let _ = socket.send(Message::Text(format!("eta_{eta}_{uuid}"))).await; | ||
89 | 93 | ||
90 | let device_exists = state.ping_map.contains_key(&uuid); | 94 | impl Display for BroadcastCommand { |
91 | if device_exists { | 95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
92 | let _ = socket.send(process_device(state.clone(), uuid).await).await; | 96 | let prefix = match self.command { |
93 | } else { | 97 | BroadcastCommands::Success => "start", |
94 | debug!("didn't find any device"); | 98 | BroadcastCommands::Timeout => "timeout", |
95 | let _ = socket.send(Message::Text(format!("notfound_{uuid}"))).await; | 99 | BroadcastCommands::Error => "error", |
96 | }; | 100 | }; |
97 | 101 | ||
98 | let _ = socket.close().await; | 102 | f.write_str(format!("{prefix}_{}", self.uuid).as_str()) |
103 | } | ||
99 | } | 104 | } |
100 | 105 | ||
101 | async fn get_eta(db: &PgPool) -> i64 { | 106 | impl BroadcastCommand { |
102 | let query = sqlx::query!( | 107 | pub fn success(uuid: String) -> Self { |
103 | r#"SELECT times FROM devices;"# | 108 | Self { |
104 | ).fetch_one(db).await.unwrap(); | 109 | uuid, |
105 | 110 | command: BroadcastCommands::Success, | |
106 | let times = match query.times { | 111 | } |
107 | None => { vec![0] }, | 112 | } |
108 | Some(t) => t, | ||
109 | }; | ||
110 | times.iter().sum::<i64>() / i64::try_from(times.len()).unwrap() | ||
111 | 113 | ||
112 | } | 114 | pub fn timeout(uuid: String) -> Self { |
115 | Self { | ||
116 | uuid, | ||
117 | command: BroadcastCommands::Timeout, | ||
118 | } | ||
119 | } | ||
113 | 120 | ||
114 | async fn process_device(state: Arc<AppState>, uuid: String) -> Message { | 121 | pub fn error(uuid: String) -> Self { |
115 | let pm = state.ping_map.clone().into_read_only(); | 122 | Self { |
116 | let device = pm.get(&uuid).expect("fatal error"); | 123 | uuid, |
117 | debug!("got device: {} (online: {})", device.ip, device.online); | 124 | command: BroadcastCommands::Error, |
118 | if device.online { | ||
119 | debug!("already started"); | ||
120 | Message::Text(format!("start_{uuid}")) | ||
121 | } else { | ||
122 | loop { | ||
123 | trace!("wait for tx message"); | ||
124 | let message = state.ping_send.subscribe().recv().await.expect("fatal error"); | ||
125 | trace!("got message {:?}", message); | ||
126 | return match message { | ||
127 | BroadcastCommands::Success(msg_uuid) => { | ||
128 | if msg_uuid != uuid { continue; } | ||
129 | trace!("message == uuid success"); | ||
130 | Message::Text(format!("start_{uuid}")) | ||
131 | }, | ||
132 | BroadcastCommands::Timeout(msg_uuid) => { | ||
133 | if msg_uuid != uuid { continue; } | ||
134 | trace!("message == uuid timeout"); | ||
135 | Message::Text(format!("timeout_{uuid}")) | ||
136 | }, | ||
137 | BroadcastCommands::Error(msg_uuid) => { | ||
138 | if msg_uuid != uuid { continue; } | ||
139 | trace!("message == uuid error"); | ||
140 | Message::Text(format!("error_{uuid}")) | ||
141 | } | ||
142 | } | ||
143 | } | 125 | } |
144 | } | 126 | } |
145 | } | 127 | } |