From 09ad929a3d71579c07bbe68c3968839327dc3810 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 16 Aug 2026 07:08:52 +0200 Subject: [PATCH 1/9] Fix config --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7bf6349..5e4793b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,9 +22,9 @@ impl Config { port: 3415, - public_hostname: "localhost:3000".to_string(), + public_hostname: "localhost:3415".to_string(), - hostnames: HashSet::from_iter(["localhost:3000".to_string()]), + hostnames: HashSet::from_iter(["localhost:3415".to_string()]), } } From 5c80cacbff7257a92b76eb91e42122ce385a9e58 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 16 Aug 2026 07:17:28 +0200 Subject: [PATCH 2/9] Fix timestamp --- src/protocol/initialize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol/initialize.rs b/src/protocol/initialize.rs index 2a50b8b..e72f910 100644 --- a/src/protocol/initialize.rs +++ b/src/protocol/initialize.rs @@ -37,9 +37,9 @@ impl UserConnections { )); }; - let server_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let server_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64; - if server_timestamp - timestamp > 2 { + if server_timestamp.saturating_sub(timestamp) > 2000 { send_socket( &mut socket, &ClientMethod::Error { From ddc8b0bf0f0dd545d2874860b5b95bfc65fd44f9 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 16 Aug 2026 07:19:06 +0200 Subject: [PATCH 3/9] Fix hostname --- src/protocol/initialize.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/protocol/initialize.rs b/src/protocol/initialize.rs index e72f910..eb059e6 100644 --- a/src/protocol/initialize.rs +++ b/src/protocol/initialize.rs @@ -53,7 +53,8 @@ impl UserConnections { return Err(anyhow::anyhow!("Client tampstamp wasn't correct")); } - if !server.config.hostnames.contains(&hostname) { + if hostname != server.config.public_hostname || !server.config.hostnames.contains(&hostname) + { send_socket( &mut socket, &ClientMethod::Error { From 0aefaa4c186c338d24755cabd531005834db811a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 17 Aug 2026 04:49:54 +0200 Subject: [PATCH 4/9] Client cleanup --- src/server.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/server.rs b/src/server.rs index f76a9e4..426d1da 100644 --- a/src/server.rs +++ b/src/server.rs @@ -49,7 +49,7 @@ impl Server { let mut clients_meta = s.clients.lock().await; - let client_meta = + let clients = clients_meta .entry(public_key) .or_insert_with(|| UserConnections { @@ -59,18 +59,23 @@ impl Server { connections: HashMap::new(), }); - client_meta.connections.insert( - client_meta - .counter - .fetch_add(1, std::sync::atomic::Ordering::Relaxed), - client.clone(), - ); + let conid = clients + .counter + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + clients.connections.insert(conid, client.clone()); if let Err(e) = read_loop(&client).await { eprintln!("Failed to handle client: {e}"); } else { println!("Client connection closed") } + + clients.connections.remove(&conid); + + if clients.connections.len() == 0 { + clients_meta.remove(&public_key); + } } Err(e) => { From be266c571d4e35e692d4a80ca1702f7af183effe Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 17 Aug 2026 05:11:50 +0200 Subject: [PATCH 5/9] Fix sig --- src/protocol/initialize.rs | 7 +++---- src/signature.rs | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/protocol/initialize.rs b/src/protocol/initialize.rs index eb059e6..650cc48 100644 --- a/src/protocol/initialize.rs +++ b/src/protocol/initialize.rs @@ -101,10 +101,9 @@ impl UserConnections { &mut socket, &ClientMethod::Initialized { public_key: crate::signature::to_string(&server.key.verifying_key()), - signature: server - .key - .sign(format!("{timestamp}@{hostname}@{public_key_string}").as_bytes()) - .to_string(), + signature: crate::signature::to_string_sig(&server.key.sign( + format!("{server_timestamp}@{hostname}@{public_key_string}").as_bytes(), + )), timestamp: server_timestamp, hostname, diff --git a/src/signature.rs b/src/signature.rs index 851b9eb..fc80df4 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -41,3 +41,7 @@ pub fn from_string_sig(signature: &str) -> anyhow::Result { .map_err(|_| anyhow::anyhow!("Invalid public key"))?, )) } + +pub fn to_string_sig(signature: &Signature) -> String { + bs58::encode(signature.to_bytes()).into_string() +} From afcb96c93876325ad9a0259c62cac35cf7297130 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 17 Aug 2026 16:03:39 +0200 Subject: [PATCH 6/9] Fix invalid sig --- src/signature.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/signature.rs b/src/signature.rs index fc80df4..3dd0753 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -24,6 +24,10 @@ pub fn to_string(key: &VerifyingKey) -> String { bs58::encode(key.to_bytes()).into_string() } +pub fn to_string_sig(signature: &Signature) -> String { + bs58::encode(signature.to_bytes()).into_string() +} + pub fn from_string(key: &str) -> anyhow::Result { Ok(VerifyingKey::from_bytes( &bs58::decode(key) @@ -38,10 +42,6 @@ pub fn from_string_sig(signature: &str) -> anyhow::Result { &bs58::decode(signature) .into_vec()? .try_into() - .map_err(|_| anyhow::anyhow!("Invalid public key"))?, + .map_err(|_| anyhow::anyhow!("Invalid signature"))?, )) } - -pub fn to_string_sig(signature: &Signature) -> String { - bs58::encode(signature.to_bytes()).into_string() -} From 7b63911a7d92d705d7853bfcf725dc765b76d91c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 18 Aug 2026 13:58:17 +0200 Subject: [PATCH 7/9] Icon --- .DS_Store | Bin 0 -> 6148 bytes Cargo.lock | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + icon.png | Bin 0 -> 5094 bytes src/main.rs | 2 ++ 5 files changed, 70 insertions(+) create mode 100644 .DS_Store create mode 100644 icon.png diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..70a47c54e6cad3d8f4e5b3e5307c27e071a287c8 GIT binary patch literal 6148 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 anyhow::Result<()> { let app = Router::new() .route("/meta", get(meta)) .route("/", any(ws_handler)) + .route_service("/icon", ServeFile::new("./icon.png")) .with_state(server.clone()); let listener = tokio::net::TcpListener::bind(SocketAddr::new( From e5c5d7265a713c6b0194751fbf2c605ef9f15e22 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 18 Aug 2026 22:55:45 +0200 Subject: [PATCH 8/9] cors --- Cargo.toml | 2 +- src/main.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2df5dc5..ec9225b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ tokio = { version = "1.53.1", features = ["rt", "rt-multi-thread", "macros", "sy ed25519-dalek = { version = "2", features = ["rand_core"] } rand = "0.8" bs58 = "0.5.1" -tower-http = { version = "0.7.0", features = ["fs"] } +tower-http = { version = "0.7.0", features = ["fs", "cors"] } diff --git a/src/main.rs b/src/main.rs index 467870b..8897bfd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,10 @@ use axum::{ response::Response, routing::{any, get}, }; -use tower_http::services::ServeFile; +use tower_http::{ + cors::{Any, CorsLayer}, + services::ServeFile, +}; use crate::server::Server; @@ -22,11 +25,17 @@ use crate::server::Server; async fn main() -> anyhow::Result<()> { let server = Server::new().await?; + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + let app = Router::new() .route("/meta", get(meta)) .route("/", any(ws_handler)) .route_service("/icon", ServeFile::new("./icon.png")) - .with_state(server.clone()); + .with_state(server.clone()) + .layer(cors); let listener = tokio::net::TcpListener::bind(SocketAddr::new( IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), From c37e9d0fde94698a53187ae9a21a77810dcd1bd0 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 19 Aug 2026 03:10:35 +0200 Subject: [PATCH 9/9] Channelsc --- src/config.rs | 15 ++++++++++++++- src/main.rs | 1 + src/protocol/mod.rs | 11 ++--------- src/server.rs | 3 ++- src/types.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 src/types.rs diff --git a/src/config.rs b/src/config.rs index 5e4793b..ea4141b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; -use crate::protocol::ServerMeta; +use crate::types::{Channel, ChannelKind, ServerMeta}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { @@ -18,6 +18,19 @@ impl Config { meta: ServerMeta { name: "New Server".to_string(), description: String::new(), + + channels: vec![Channel { + id: "text-channels".to_string(), + name: "Text Channels".to_string(), + + data: ChannelKind::Category { + channels: vec![Channel { + id: "general".to_string(), + name: "General".to_string(), + data: ChannelKind::Text, + }], + }, + }], }, port: 3415, diff --git a/src/main.rs b/src/main.rs index 8897bfd..25696ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ pub mod config; pub mod protocol; pub mod server; pub mod signature; +pub mod types; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 0503573..a8ee7c5 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -4,17 +4,10 @@ use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; +use crate::types::ClientMeta; + pub mod initialize; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClientMeta {} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ServerMeta { - pub name: String, - pub description: String, -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "method")] pub enum ClientMethod { diff --git a/src/server.rs b/src/server.rs index 426d1da..41365e0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -12,7 +12,8 @@ use tokio::sync::Mutex; use crate::{ config::Config, - protocol::{ClientMeta, ClientMethod, read_loop, send_socket}, + protocol::{ClientMethod, read_loop, send_socket}, + types::ClientMeta, }; pub struct UserConnections { diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..98fde83 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientMeta {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerMeta { + pub name: String, + pub description: String, + pub channels: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "kind")] +#[serde(rename_all = "camelCase")] +pub enum ChannelKind { + Text, + Category { channels: Vec }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Channel { + pub id: String, + pub name: String, + + #[serde(flatten)] + pub data: ChannelKind, +}