diff options
-rw-r--r-- | Cargo.lock | 21 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | protocol.md | 1 | ||||
-rw-r--r-- | server/discover/Cargo.toml | 19 | ||||
-rw-r--r-- | server/discover/src/main.rs | 136 |
5 files changed, 176 insertions, 2 deletions
@@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arbitrary" @@ -1170,6 +1170,22 @@ dependencies = [ ] [[package]] +name = "hurrycurry-discover" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "http-body-util", + "hurrycurry-protocol", + "hyper 1.4.1", + "hyper-util", + "log", + "mdns-sd", + "serde_json", + "tokio", +] + +[[package]] name = "hurrycurry-protocol" version = "0.1.0" dependencies = [ @@ -1280,6 +1296,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -6,6 +6,7 @@ members = [ "server/client-lib", "server/replaytool", "server/registry", + "server/discover", "pixel-client", "pixel-client/tools", "locale/tools", diff --git a/protocol.md b/protocol.md index 1abdfc36..4e5ab475 100644 --- a/protocol.md +++ b/protocol.md @@ -47,6 +47,7 @@ sending a packet to update that of the other player. - 27032: Game Server Websocket - 27033: Registry API HTTP - 27034: Lobby Server Websocket +- 27035: Local discovery service API ## Binary Protocol 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}"); + } + }); + } +} |