aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/auth.rs12
-rw-r--r--src/error.rs8
-rw-r--r--src/main.rs14
-rw-r--r--src/routes/device.rs26
-rw-r--r--src/routes/start.rs72
-rw-r--r--src/routes/status.rs2
-rw-r--r--src/services/ping.rs14
-rw-r--r--src/wol.rs14
8 files changed, 94 insertions, 68 deletions
diff --git a/src/auth.rs b/src/auth.rs
index 90d920f..0321ade 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,17 +1,17 @@
1use axum::http::{StatusCode, HeaderValue}; 1use axum::http::{StatusCode, HeaderValue};
2use axum::http::header::ToStrError; 2use axum::http::header::ToStrError;
3use tracing::{debug, error, trace}; 3use tracing::{debug, error, trace};
4use crate::auth::AuthError::{MissingSecret, WrongSecret}; 4use crate::auth::Error::{MissingSecret, WrongSecret};
5use crate::config::SETTINGS; 5use crate::config::SETTINGS;
6 6
7pub fn auth(secret: Option<&HeaderValue>) -> Result<bool, AuthError> { 7pub fn auth(secret: Option<&HeaderValue>) -> Result<bool, Error> {
8 debug!("auth request with secret {:?}", secret); 8 debug!("auth request with secret {:?}", secret);
9 if let Some(value) = secret { 9 if let Some(value) = secret {
10 trace!("value exists"); 10 trace!("value exists");
11 let key = SETTINGS 11 let key = SETTINGS
12 .get_string("apikey") 12 .get_string("apikey")
13 .map_err(AuthError::Config)?; 13 .map_err(Error::Config)?;
14 if value.to_str().map_err(AuthError::HeaderToStr)? == key.as_str() { 14 if value.to_str().map_err(Error::HeaderToStr)? == key.as_str() {
15 debug!("successful auth"); 15 debug!("successful auth");
16 Ok(true) 16 Ok(true)
17 } else { 17 } else {
@@ -25,14 +25,14 @@ pub fn auth(secret: Option<&HeaderValue>) -> Result<bool, AuthError> {
25} 25}
26 26
27#[derive(Debug)] 27#[derive(Debug)]
28pub enum AuthError { 28pub enum Error {
29 WrongSecret, 29 WrongSecret,
30 MissingSecret, 30 MissingSecret,
31 Config(config::ConfigError), 31 Config(config::ConfigError),
32 HeaderToStr(ToStrError) 32 HeaderToStr(ToStrError)
33} 33}
34 34
35impl AuthError { 35impl Error {
36 pub fn get(self) -> (StatusCode, &'static str) { 36 pub fn get(self) -> (StatusCode, &'static str) {
37 match self { 37 match self {
38 Self::WrongSecret => (StatusCode::UNAUTHORIZED, "Wrong credentials"), 38 Self::WrongSecret => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
diff --git a/src/error.rs b/src/error.rs
index 5b82534..56d6c52 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -4,10 +4,10 @@ use axum::Json;
4use axum::response::{IntoResponse, Response}; 4use axum::response::{IntoResponse, Response};
5use serde_json::json; 5use serde_json::json;
6use tracing::error; 6use tracing::error;
7use crate::auth::AuthError; 7use crate::auth::Error as AuthError;
8 8
9#[derive(Debug)] 9#[derive(Debug)]
10pub enum WebolError { 10pub enum Error {
11 Generic, 11 Generic,
12 Auth(AuthError), 12 Auth(AuthError),
13 DB(sqlx::Error), 13 DB(sqlx::Error),
@@ -16,7 +16,7 @@ pub enum WebolError {
16 Broadcast(io::Error), 16 Broadcast(io::Error),
17} 17}
18 18
19impl IntoResponse for WebolError { 19impl IntoResponse for Error {
20 fn into_response(self) -> Response { 20 fn into_response(self) -> Response {
21 let (status, error_message) = match self { 21 let (status, error_message) = match self {
22 Self::Auth(err) => { 22 Self::Auth(err) => {
@@ -45,4 +45,4 @@ impl IntoResponse for WebolError {
45 })); 45 }));
46 (status, body).into_response() 46 (status, body).into_response()
47 } 47 }
48} \ No newline at end of file 48}
diff --git a/src/main.rs b/src/main.rs
index aab9df3..9d30548 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,10 +11,10 @@ use tracing::{info, level_filters::LevelFilter};
11use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*}; 11use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*};
12use crate::config::SETTINGS; 12use crate::config::SETTINGS;
13use crate::db::init_db_pool; 13use crate::db::init_db_pool;
14use crate::routes::device::{get_device, post_device, put_device}; 14use crate::routes::device;
15use crate::routes::start::start; 15use crate::routes::start::start;
16use crate::routes::status::status; 16use crate::routes::status::status;
17use crate::services::ping::{BroadcastCommands, PingMap}; 17use crate::services::ping::{BroadcastCommands, StatusMap};
18 18
19mod auth; 19mod auth;
20mod config; 20mod config;
@@ -54,15 +54,15 @@ async fn main() -> color_eyre::eyre::Result<()> {
54 54
55 let (tx, _) = channel(32); 55 let (tx, _) = channel(32);
56 56
57 let ping_map: PingMap = DashMap::new(); 57 let ping_map: StatusMap = DashMap::new();
58 58
59 let shared_state = Arc::new(AppState { db, ping_send: tx, ping_map }); 59 let shared_state = Arc::new(AppState { db, ping_send: tx, ping_map });
60 60
61 let app = Router::new() 61 let app = Router::new()
62 .route("/start", post(start)) 62 .route("/start", post(start))
63 .route("/device", get(get_device)) 63 .route("/device", get(device::get))
64 .route("/device", put(put_device)) 64 .route("/device", put(device::put))
65 .route("/device", post(post_device)) 65 .route("/device", post(device::post))
66 .route("/status", get(status)) 66 .route("/status", get(status))
67 .with_state(shared_state); 67 .with_state(shared_state);
68 68
@@ -78,5 +78,5 @@ async fn main() -> color_eyre::eyre::Result<()> {
78pub struct AppState { 78pub struct AppState {
79 db: PgPool, 79 db: PgPool,
80 ping_send: Sender<BroadcastCommands>, 80 ping_send: Sender<BroadcastCommands>,
81 ping_map: PingMap, 81 ping_map: StatusMap,
82} 82}
diff --git a/src/routes/device.rs b/src/routes/device.rs
index a3308d4..b80cb85 100644
--- a/src/routes/device.rs
+++ b/src/routes/device.rs
@@ -7,12 +7,12 @@ use serde_json::{json, Value};
7use tracing::{debug, info}; 7use tracing::{debug, info};
8use crate::auth::auth; 8use crate::auth::auth;
9use crate::db::Device; 9use crate::db::Device;
10use crate::error::WebolError; 10use crate::error::Error;
11 11
12pub async fn get_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, WebolError> { 12pub async fn get(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, Error> {
13 info!("add device {}", payload.id); 13 info!("add device {}", payload.id);
14 let secret = headers.get("authorization"); 14 let secret = headers.get("authorization");
15 if auth(secret).map_err(WebolError::Auth)? { 15 if auth(secret).map_err(Error::Auth)? {
16 let device = sqlx::query_as!( 16 let device = sqlx::query_as!(
17 Device, 17 Device,
18 r#" 18 r#"
@@ -21,13 +21,13 @@ pub async fn get_device(State(state): State<Arc<crate::AppState>>, headers: Head
21 WHERE id = $1; 21 WHERE id = $1;
22 "#, 22 "#,
23 payload.id 23 payload.id
24 ).fetch_one(&state.db).await.map_err(WebolError::DB)?; 24 ).fetch_one(&state.db).await.map_err(Error::DB)?;
25 25
26 debug!("got device {:?}", device); 26 debug!("got device {:?}", device);
27 27
28 Ok(Json(json!(device))) 28 Ok(Json(json!(device)))
29 } else { 29 } else {
30 Err(WebolError::Generic) 30 Err(Error::Generic)
31 } 31 }
32} 32}
33 33
@@ -36,10 +36,10 @@ pub struct GetDevicePayload {
36 id: String, 36 id: String,
37} 37}
38 38
39pub async fn put_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, WebolError> { 39pub async fn put(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, Error> {
40 info!("add device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); 40 info!("add device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip);
41 let secret = headers.get("authorization"); 41 let secret = headers.get("authorization");
42 if auth(secret).map_err(WebolError::Auth)? { 42 if auth(secret).map_err(Error::Auth)? {
43 sqlx::query!( 43 sqlx::query!(
44 r#" 44 r#"
45 INSERT INTO devices (id, mac, broadcast_addr, ip) 45 INSERT INTO devices (id, mac, broadcast_addr, ip)
@@ -49,11 +49,11 @@ pub async fn put_device(State(state): State<Arc<crate::AppState>>, headers: Head
49 payload.mac, 49 payload.mac,
50 payload.broadcast_addr, 50 payload.broadcast_addr,
51 payload.ip 51 payload.ip
52 ).execute(&state.db).await.map_err(WebolError::DB)?; 52 ).execute(&state.db).await.map_err(Error::DB)?;
53 53
54 Ok(Json(json!(PutDeviceResponse { success: true }))) 54 Ok(Json(json!(PutDeviceResponse { success: true })))
55 } else { 55 } else {
56 Err(WebolError::Generic) 56 Err(Error::Generic)
57 } 57 }
58} 58}
59 59
@@ -70,10 +70,10 @@ pub struct PutDeviceResponse {
70 success: bool 70 success: bool
71} 71}
72 72
73pub async fn post_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, WebolError> { 73pub async fn post(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, Error> {
74 info!("edit device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); 74 info!("edit device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip);
75 let secret = headers.get("authorization"); 75 let secret = headers.get("authorization");
76 if auth(secret).map_err(WebolError::Auth)? { 76 if auth(secret).map_err(Error::Auth)? {
77 let device = sqlx::query_as!( 77 let device = sqlx::query_as!(
78 Device, 78 Device,
79 r#" 79 r#"
@@ -85,11 +85,11 @@ pub async fn post_device(State(state): State<Arc<crate::AppState>>, headers: Hea
85 payload.broadcast_addr, 85 payload.broadcast_addr,
86 payload.ip, 86 payload.ip,
87 payload.id 87 payload.id
88 ).fetch_one(&state.db).await.map_err(WebolError::DB)?; 88 ).fetch_one(&state.db).await.map_err(Error::DB)?;
89 89
90 Ok(Json(json!(device))) 90 Ok(Json(json!(device)))
91 } else { 91 } else {
92 Err(WebolError::Generic) 92 Err(Error::Generic)
93 } 93 }
94} 94}
95 95
diff --git a/src/routes/start.rs b/src/routes/start.rs
index a206cbd..4264588 100644
--- a/src/routes/start.rs
+++ b/src/routes/start.rs
@@ -1,23 +1,27 @@
1use axum::Json; 1use crate::auth::auth;
2use crate::config::SETTINGS;
3use crate::db::Device;
4use crate::error::Error;
5use crate::services::ping::Value as PingValue;
6use crate::wol::{create_buffer, send_packet};
7use axum::extract::State;
2use axum::http::HeaderMap; 8use axum::http::HeaderMap;
9use axum::Json;
3use serde::{Deserialize, Serialize}; 10use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use axum::extract::State;
6use serde_json::{json, Value}; 11use serde_json::{json, Value};
12use std::sync::Arc;
7use tracing::{debug, info}; 13use tracing::{debug, info};
8use uuid::Uuid; 14use uuid::Uuid;
9use crate::auth::auth;
10use crate::config::SETTINGS;
11use crate::wol::{create_buffer, send_packet};
12use crate::db::Device;
13use crate::error::WebolError;
14use crate::services::ping::PingValue;
15 15
16#[axum_macros::debug_handler] 16#[axum_macros::debug_handler]
17pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<StartPayload>) -> Result<Json<Value>, WebolError> { 17pub async fn start(
18 State(state): State<Arc<crate::AppState>>,
19 headers: HeaderMap,
20 Json(payload): Json<Payload>,
21) -> Result<Json<Value>, Error> {
18 info!("POST request"); 22 info!("POST request");
19 let secret = headers.get("authorization"); 23 let secret = headers.get("authorization");
20 let authorized = auth(secret).map_err(WebolError::Auth)?; 24 let authorized = auth(secret).map_err(Error::Auth)?;
21 if authorized { 25 if authorized {
22 let device = sqlx::query_as!( 26 let device = sqlx::query_as!(
23 Device, 27 Device,
@@ -27,7 +31,10 @@ pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap
27 WHERE id = $1; 31 WHERE id = $1;
28 "#, 32 "#,
29 payload.id 33 payload.id
30 ).fetch_one(&state.db).await.map_err(WebolError::DB)?; 34 )
35 .fetch_one(&state.db)
36 .await
37 .map_err(Error::DB)?;
31 38
32 info!("starting {}", device.id); 39 info!("starting {}", device.id);
33 40
@@ -36,9 +43,9 @@ pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap
36 .unwrap_or("0.0.0.0:1111".to_string()); 43 .unwrap_or("0.0.0.0:1111".to_string());
37 44
38 let _ = send_packet( 45 let _ = send_packet(
39 &bind_addr.parse().map_err(WebolError::IpParse)?, 46 &bind_addr.parse().map_err(Error::IpParse)?,
40 &device.broadcast_addr.parse().map_err(WebolError::IpParse)?, 47 &device.broadcast_addr.parse().map_err(Error::IpParse)?,
41 create_buffer(&device.mac)? 48 &create_buffer(&device.mac)?,
42 )?; 49 )?;
43 let dev_id = device.id.clone(); 50 let dev_id = device.id.clone();
44 let uuid = if payload.ping.is_some_and(|ping| ping) { 51 let uuid = if payload.ping.is_some_and(|ping| ping) {
@@ -49,7 +56,7 @@ pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap
49 uuid = Some(key); 56 uuid = Some(key);
50 break; 57 break;
51 } 58 }
52 }; 59 }
53 let uuid_gen = match uuid { 60 let uuid_gen = match uuid {
54 Some(u) => u, 61 Some(u) => u,
55 None => Uuid::new_v4().to_string(), 62 None => Uuid::new_v4().to_string(),
@@ -58,26 +65,45 @@ pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap
58 65
59 tokio::spawn(async move { 66 tokio::spawn(async move {
60 debug!("init ping service"); 67 debug!("init ping service");
61 state.ping_map.insert(uuid_gen.clone(), PingValue { ip: device.ip.clone(), online: false }); 68 state.ping_map.insert(
69 uuid_gen.clone(),
70 PingValue {
71 ip: device.ip.clone(),
72 online: false,
73 },
74 );
62 75
63 crate::services::ping::spawn(state.ping_send.clone(), device, uuid_gen.clone(), &state.ping_map, &state.db).await; 76 crate::services::ping::spawn(
77 state.ping_send.clone(),
78 device,
79 uuid_gen.clone(),
80 &state.ping_map,
81 &state.db,
82 )
83 .await;
64 }); 84 });
65 Some(uuid_genc) 85 Some(uuid_genc)
66 } else { None }; 86 } else {
67 Ok(Json(json!(StartResponse { id: dev_id, boot: true, uuid }))) 87 None
88 };
89 Ok(Json(json!(Response {
90 id: dev_id,
91 boot: true,
92 uuid
93 })))
68 } else { 94 } else {
69 Err(WebolError::Generic) 95 Err(Error::Generic)
70 } 96 }
71} 97}
72 98
73#[derive(Deserialize)] 99#[derive(Deserialize)]
74pub struct StartPayload { 100pub struct Payload {
75 id: String, 101 id: String,
76 ping: Option<bool>, 102 ping: Option<bool>,
77} 103}
78 104
79#[derive(Serialize)] 105#[derive(Serialize)]
80struct StartResponse { 106struct Response {
81 id: String, 107 id: String,
82 boot: bool, 108 boot: bool,
83 uuid: Option<String>, 109 uuid: Option<String>,
diff --git a/src/routes/status.rs b/src/routes/status.rs
index 45f3e51..31ef996 100644
--- a/src/routes/status.rs
+++ b/src/routes/status.rs
@@ -7,4 +7,4 @@ use crate::services::ping::status_websocket;
7#[axum_macros::debug_handler] 7#[axum_macros::debug_handler]
8pub async fn status(State(state): State<Arc<AppState>>, ws: WebSocketUpgrade) -> Response { 8pub async fn status(State(state): State<Arc<AppState>>, ws: WebSocketUpgrade) -> Response {
9 ws.on_upgrade(move |socket| status_websocket(socket, state)) 9 ws.on_upgrade(move |socket| status_websocket(socket, state))
10} \ No newline at end of file 10}
diff --git a/src/services/ping.rs b/src/services/ping.rs
index 0f773f4..7d71218 100644
--- a/src/services/ping.rs
+++ b/src/services/ping.rs
@@ -2,26 +2,26 @@ use std::str::FromStr;
2use std::net::IpAddr; 2use std::net::IpAddr;
3use std::sync::Arc; 3use std::sync::Arc;
4 4
5use axum::extract::{ws::WebSocket}; 5use axum::extract::ws::WebSocket;
6use axum::extract::ws::Message; 6use axum::extract::ws::Message;
7use dashmap::DashMap; 7use dashmap::DashMap;
8use sqlx::PgPool; 8use sqlx::PgPool;
9use time::{Duration, Instant}; 9use time::{Duration, Instant};
10use tokio::sync::broadcast::{Sender}; 10use tokio::sync::broadcast::Sender;
11use tracing::{debug, error, trace}; 11use tracing::{debug, error, trace};
12use crate::AppState; 12use crate::AppState;
13use crate::config::SETTINGS; 13use crate::config::SETTINGS;
14use crate::db::Device; 14use crate::db::Device;
15 15
16pub type PingMap = DashMap<String, PingValue>; 16pub type StatusMap = DashMap<String, Value>;
17 17
18#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
19pub struct PingValue { 19pub struct Value {
20 pub ip: String, 20 pub ip: String,
21 pub online: bool 21 pub online: bool
22} 22}
23 23
24pub async fn spawn(tx: Sender<BroadcastCommands>, device: Device, uuid: String, ping_map: &PingMap, db: &PgPool) { 24pub async fn spawn(tx: Sender<BroadcastCommands>, device: Device, uuid: String, ping_map: &StatusMap, db: &PgPool) {
25 let timer = Instant::now(); 25 let timer = Instant::now();
26 let payload = [0; 8]; 26 let payload = [0; 8];
27 27
@@ -63,7 +63,7 @@ pub async fn spawn(tx: Sender<BroadcastCommands>, device: Device, uuid: String,
63 timer.elapsed().whole_seconds(), 63 timer.elapsed().whole_seconds(),
64 device.id 64 device.id
65 ).execute(db).await.unwrap(); 65 ).execute(db).await.unwrap();
66 ping_map.insert(uuid.clone(), PingValue { ip: device.ip.clone(), online: true }); 66 ping_map.insert(uuid.clone(), Value { ip: device.ip.clone(), online: true });
67 tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; 67 tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
68 } 68 }
69 trace!("remove {} from ping_map", uuid); 69 trace!("remove {} from ping_map", uuid);
@@ -107,7 +107,7 @@ async fn get_eta(db: &PgPool) -> i64 {
107 None => { vec![0] }, 107 None => { vec![0] },
108 Some(t) => t, 108 Some(t) => t,
109 }; 109 };
110 times.iter().sum::<i64>() / times.len() as i64 110 times.iter().sum::<i64>() / i64::try_from(times.len()).unwrap()
111 111
112} 112}
113 113
diff --git a/src/wol.rs b/src/wol.rs
index 8755b21..83c0ee6 100644
--- a/src/wol.rs
+++ b/src/wol.rs
@@ -1,17 +1,17 @@
1use std::net::{SocketAddr, UdpSocket}; 1use std::net::{SocketAddr, UdpSocket};
2 2
3use crate::error::WebolError; 3use crate::error::Error;
4 4
5/// Creates the magic packet from a mac address 5/// Creates the magic packet from a mac address
6/// 6///
7/// # Panics 7/// # Panics
8/// 8///
9/// Panics if `mac_addr` is an invalid mac 9/// Panics if `mac_addr` is an invalid mac
10pub fn create_buffer(mac_addr: &str) -> Result<Vec<u8>, WebolError> { 10pub fn create_buffer(mac_addr: &str) -> Result<Vec<u8>, Error> {
11 let mut mac = Vec::new(); 11 let mut mac = Vec::new();
12 let sp = mac_addr.split(':'); 12 let sp = mac_addr.split(':');
13 for f in sp { 13 for f in sp {
14 mac.push(u8::from_str_radix(f, 16).map_err(WebolError::BufferParse)?); 14 mac.push(u8::from_str_radix(f, 16).map_err(Error::BufferParse)?);
15 }; 15 };
16 let mut buf = vec![255; 6]; 16 let mut buf = vec![255; 6];
17 for _ in 0..16 { 17 for _ in 0..16 {
@@ -23,8 +23,8 @@ pub fn create_buffer(mac_addr: &str) -> Result<Vec<u8>, WebolError> {
23} 23}
24 24
25/// Sends a buffer on UDP broadcast 25/// Sends a buffer on UDP broadcast
26pub fn send_packet(bind_addr: &SocketAddr, broadcast_addr: &SocketAddr, buffer: Vec<u8>) -> Result<usize, WebolError> { 26pub fn send_packet(bind_addr: &SocketAddr, broadcast_addr: &SocketAddr, buffer: &[u8]) -> Result<usize, Error> {
27 let socket = UdpSocket::bind(bind_addr).map_err(WebolError::Broadcast)?; 27 let socket = UdpSocket::bind(bind_addr).map_err(Error::Broadcast)?;
28 socket.set_broadcast(true).map_err(WebolError::Broadcast)?; 28 socket.set_broadcast(true).map_err(Error::Broadcast)?;
29 socket.send_to(&buffer, broadcast_addr).map_err(WebolError::Broadcast) 29 socket.send_to(buffer, broadcast_addr).map_err(Error::Broadcast)
30} 30}