diff --git a/Cargo.lock b/Cargo.lock index 62c05f4..25589f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytes" version = "1.12.1" @@ -256,6 +262,7 @@ dependencies = [ "serde_json", "tokio", "tower-http", + "uuid", ] [[package]] @@ -360,10 +367,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -474,6 +492,17 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.189" @@ -603,6 +632,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.7" @@ -685,6 +720,12 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "ryu" version = "1.0.23" @@ -1059,6 +1100,17 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -1086,6 +1138,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 4cd58c6..219512b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ rand = "0.8" bs58 = "0.5.1" tower-http = { version = "0.7.0", features = ["fs", "cors"] } rusqlite = { version = "0.31", features = ["bundled"] } +uuid = { version = "1.24.1", features = ["v4"] } diff --git a/src/data/messages.rs b/src/data/messages.rs index 728f684..d2ecf22 100644 --- a/src/data/messages.rs +++ b/src/data/messages.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use rusqlite::{params, Connection}; +use rusqlite::{Connection, params}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Mutex; @@ -9,14 +10,19 @@ pub struct MessageStore { connections: Mutex>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageData { + pub content: String, + pub timestamp: u64, + pub signature: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct StoredMessage { pub id: String, - pub author_pubkey: String, - pub content: String, - pub timestamp: i64, - pub signature: String, - pub channel_id: String, + pub author: String, + #[serde(flatten)] + pub data: MessageData, } impl MessageStore { @@ -48,7 +54,6 @@ impl MessageStore { content TEXT NOT NULL, timestamp INTEGER NOT NULL, signature TEXT NOT NULL, - channel_id TEXT NOT NULL )", [], )?; @@ -68,15 +73,14 @@ impl MessageStore { pub fn insert_message(&self, channel_id: &str, msg: &StoredMessage) -> Result<()> { self.with_channel(channel_id, |conn| { conn.execute( - "INSERT INTO messages (id, author_pubkey, content, timestamp, signature, channel_id) + "INSERT INTO messages (id, author_pubkey, content, timestamp, signature) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![ msg.id, - msg.author_pubkey, - msg.content, - msg.timestamp, - msg.signature, - msg.channel_id, + msg.author, + msg.data.content, + msg.data.timestamp, + msg.data.signature, ], )?; Ok(()) @@ -87,7 +91,7 @@ impl MessageStore { pub fn get_recent_messages(&self, channel_id: &str, limit: u32) -> Result> { self.with_channel(channel_id, |conn| { let mut stmt = conn.prepare( - "SELECT id, author_pubkey, content, timestamp, signature, channel_id + "SELECT id, author_pubkey, content, timestamp, signature FROM messages ORDER BY timestamp DESC LIMIT ?1", @@ -96,11 +100,12 @@ impl MessageStore { let rows = stmt.query_map(params![limit], |row| { Ok(StoredMessage { id: row.get(0)?, - author_pubkey: row.get(1)?, - content: row.get(2)?, - timestamp: row.get(3)?, - signature: row.get(4)?, - channel_id: row.get(5)?, + author: row.get(1)?, + data: MessageData { + content: row.get(2)?, + timestamp: row.get(3)?, + signature: row.get(4)?, + }, }) })?; diff --git a/src/protocol/message.rs b/src/protocol/message.rs index e69de29..de7d09b 100644 --- a/src/protocol/message.rs +++ b/src/protocol/message.rs @@ -0,0 +1,47 @@ +use crate::data::messages::{MessageData, StoredMessage}; +use crate::server::Server; +use axum::extract::ws::WebSocket; +use ed25519_dalek::{Signature, Verifier, VerifyingKey}; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::sync::Mutex; + +pub async fn send_message( + server: &Arc, + verifying_key: VerifyingKey, + socket: &Arc>, + message: MessageData, + channel_id: String, +) -> anyhow::Result<()> { + let server_timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64; + + if server_timestamp.saturating_sub(message.timestamp) > 2000 { + anyhow::bail!( + "Message timestamp out of range ({server_timestamp} - {}) < 2000", + message.timestamp + ); + } + + let server_pubkey_string = crate::signature::to_string(&server.key.verifying_key()); + let signed_string = format!( + "{}@{}@{}", + message.timestamp, server_pubkey_string, message.content + ); + + let signature = crate::signature::from_string_sig(&message.signature) + .map_err(|_| anyhow::anyhow!("Invalid signature encoding"))?; + + verifying_key + .verify(signed_string.as_bytes(), &signature) + .map_err(|_| anyhow::anyhow!("Signature verification failed"))?; + + let stored = StoredMessage { + id: uuid::Uuid::new_v4().to_string(), + author: crate::signature::to_string(&verifying_key), + data: message, + }; + + server.message_store.insert_message(&channel_id, &stored)?; + + Ok(()) +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index a8ee7c5..d3eead3 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,12 +1,14 @@ use std::{borrow::Cow, sync::Arc}; use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; +use ed25519_dalek::VerifyingKey; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; -use crate::types::ClientMeta; +use crate::{server::Server, types::ClientMeta}; pub mod initialize; +pub mod message; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "method")] @@ -35,6 +37,11 @@ pub enum ServerMethod { hostname: String, }, + SendMessage { + channel_id: String, + data: crate::data::messages::MessageData, + }, + Meta(ClientMeta), Error { @@ -42,7 +49,11 @@ pub enum ServerMethod { }, } -pub async fn read_loop(socket: &Arc>) -> anyhow::Result<()> { +pub async fn read_loop( + server: &Arc, + verifying_key: VerifyingKey, + socket: &Arc>, +) -> anyhow::Result<()> { while let Some(message) = read_socket(&mut *socket.lock().await).await? { match message { ServerMethod::Initialize { .. } => { @@ -61,6 +72,10 @@ pub async fn read_loop(socket: &Arc>) -> anyhow::Result<()> { ServerMethod::Error { error } => { eprintln!("Client error: {error}"); } + + ServerMethod::SendMessage { channel_id, data } => { + message::send_message(server, verifying_key, socket, data, channel_id).await?; + } } } diff --git a/src/server.rs b/src/server.rs index 02b1be6..3406455 100644 --- a/src/server.rs +++ b/src/server.rs @@ -69,7 +69,7 @@ impl Server { clients.connections.insert(conid, client.clone()); - if let Err(e) = read_loop(&client).await { + if let Err(e) = read_loop(&s, public_key, &client).await { eprintln!("Failed to handle client: {e}"); } else { println!("Client connection closed")