summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock21
-rw-r--r--Cargo.toml1
-rw-r--r--protocol.md1
-rw-r--r--server/discover/Cargo.toml19
-rw-r--r--server/discover/src/main.rs136
5 files changed, 176 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a20de0e2..b8b6fff8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index d36bc510..cbe90e84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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}");
+ }
+ });
+ }
+}