diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/discover/Cargo.toml | 19 | ||||
-rw-r--r-- | server/discover/src/main.rs | 136 |
2 files changed, 155 insertions, 0 deletions
diff --git a/server/discover/Cargo.toml b/server/discover/Cargo.toml new file mode 100644 index 00000000..8ebdef8f --- /dev/null +++ b/server/discover/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "hurrycurry-discover" +version = "0.1.0" +edition = "2021" + +[dependencies] +mdns-sd = "0.11.4" +env_logger = "0.11.5" +log = "0.4.22" +anyhow = "1.0.89" +hyper = { version = "1.4.1", default-features = false, features = [ + "server", + "http1", +] } +tokio = { version = "1.40.0", features = ["full"] } +http-body-util = "0.1" +hyper-util = { version = "0.1", features = ["full"] } +hurrycurry-protocol = { path = "../protocol" } +serde_json = "1.0.128" diff --git a/server/discover/src/main.rs b/server/discover/src/main.rs new file mode 100644 index 00000000..1c23de85 --- /dev/null +++ b/server/discover/src/main.rs @@ -0,0 +1,136 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ +#![feature(never_type)] +use anyhow::Result; +use http_body_util::Full; +use hurrycurry_protocol::registry::Entry; +use hyper::{ + body::Bytes, header::HeaderValue, server::conn::http1, service::service_fn, Response, + StatusCode, +}; +use hyper_util::rt::TokioIo; +use log::warn; +use mdns_sd::{ServiceDaemon, ServiceEvent}; +use std::{cmp::Reverse, collections::HashMap, sync::Arc}; +use tokio::{net::TcpListener, spawn, sync::RwLock}; + +fn main() -> Result<()> { + env_logger::init_from_env("LOG"); + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async_main())?; +} +async fn async_main() -> Result<!> { + let mdns = ServiceDaemon::new()?; + let mdns_events = mdns.browse("_hurrycurry._tcp.local.")?; + + let entries = Arc::new(RwLock::new(HashMap::<String, Entry>::new())); + let entries2 = entries.clone(); + + spawn(async move { + while let Ok(event) = mdns_events.recv_async().await { + match event { + ServiceEvent::ServiceResolved(service_info) => { + entries2.write().await.insert( + service_info.get_fullname().to_owned(), + Entry { + name: service_info + .get_fullname() + .strip_suffix("._hurrycurry._tcp.local.") + .unwrap_or(service_info.get_fullname()) + .to_owned(), + address: service_info + .get_addresses() + .into_iter() + .map(|a| format!("ws://{a}:{}", service_info.get_port())) + .collect(), + players_online: service_info + .get_property_val_str("players") + .and_then(|p| p.parse().ok()) + .unwrap_or(0), + last_game: 0, + version: service_info + .get_property_val_str("protocol") + .and_then(|v| { + let (maj, min) = v.split_once(".")?; + Some((maj.parse().ok()?, min.parse().ok()?)) + }) + .unwrap_or((0, 0)), + }, + ); + } + ServiceEvent::ServiceRemoved(_, fullname) => { + entries2.write().await.remove(&fullname); + } + _ => (), + } + } + }); + + let listener = TcpListener::bind("127.0.0.1:27035").await?; + loop { + let (stream, _) = listener.accept().await?; + let entries = entries.clone(); + spawn(async move { + if let Err(e) = http1::Builder::new() + .serve_connection( + TokioIo::new(stream), + service_fn(move |_req| { + let entries = entries.clone(); + async move { + Ok::<_, !>(match _req.uri().path() { + "/" => Response::new(Full::new(Bytes::from( + "Hurry Curry! local discovery service", + ))), + "/v1/list" => { + let mut ents = entries + .read() + .await + .clone() + .into_values() + .collect::<Vec<_>>(); + ents.sort_by_key(|e| Reverse(e.players_online)); + + let mut res = Response::new(Full::new(Bytes::from( + serde_json::to_string(&ents).unwrap(), + ))); + *res.status_mut() = StatusCode::OK; + res.headers_mut().insert( + "content-type", + HeaderValue::from_static("application/json"), + ); + res + } + _ => { + let mut res = + Response::new(Full::new(Bytes::from("not found"))); + *res.status_mut() = StatusCode::NOT_FOUND; + res + } + }) + } + }), + ) + .await + { + warn!("conn failed: {e}"); + } + }); + } +} |