aboutsummaryrefslogtreecommitdiff
path: root/server/discover/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/discover/src')
-rw-r--r--server/discover/src/main.rs136
1 files changed, 136 insertions, 0 deletions
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}");
+ }
+ });
+ }
+}