From db8de555339dafeed33dfd1e36ef64bb84f6349b Mon Sep 17 00:00:00 2001 From: timeshifter Date: Sat, 11 Jul 2026 17:36:35 +0200 Subject: [PATCH] add problem 03 --- problem03/Cargo.lock | 225 ++++++++++++++++++++++++++++++++++++++++++ problem03/Cargo.toml | 8 ++ problem03/src/main.rs | 175 ++++++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+) create mode 100644 problem03/Cargo.lock create mode 100644 problem03/Cargo.toml create mode 100644 problem03/src/main.rs diff --git a/problem03/Cargo.lock b/problem03/Cargo.lock new file mode 100644 index 0000000..5996a3b --- /dev/null +++ b/problem03/Cargo.lock @@ -0,0 +1,225 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "problem03" +version = "0.1.0" +dependencies = [ + "anyhow", + "tokio", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/problem03/Cargo.toml b/problem03/Cargo.toml new file mode 100644 index 0000000..0b98f5a --- /dev/null +++ b/problem03/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "problem03" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1", features = ["full"] } +anyhow = "1" diff --git a/problem03/src/main.rs b/problem03/src/main.rs new file mode 100644 index 0000000..db65297 --- /dev/null +++ b/problem03/src/main.rs @@ -0,0 +1,175 @@ +use std::sync::Arc; + +use anyhow::bail; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt, Result}, + net::TcpStream, + sync::{Mutex, MutexGuard, broadcast::Sender}, +}; + +#[tokio::main] +async fn main() -> Result<()> { + let listener = tokio::net::TcpListener::bind("0.0.0.0:10000").await?; + + let room = Arc::new(Mutex::new(vec![])); + let (tx, _) = tokio::sync::broadcast::channel(100_000); + + loop { + let (socket, _) = listener.accept().await?; + let the_room = room.clone(); + let this_tx = tx.clone(); + tokio::spawn(async move { + match handle_connection(socket, the_room, this_tx).await { + Ok(_) => (), + Err(err) => { + dbg!(err); + } + } + }); + } +} + +async fn handle_connection( + socket: TcpStream, + room: Arc>>, + tx: Sender, +) -> anyhow::Result<()> { + let (mut reader, mut writer) = tokio::io::split(socket); + + writer + .write_all(b"Welcome to budgetchat! What shall I call you?\n") + .await?; + + let mut read_buffer = Vec::with_capacity(1024); + + read_to_buffer(&mut reader, &mut read_buffer).await?; + + let newline = find_next_newline(&read_buffer).unwrap(); + let before_newline = read_buffer.drain(0..=newline).collect(); + + let self_name = String::from_utf8(before_newline)?.trim().to_string(); + + let message = generate_room_contains_message(&room).await; + writer.write_all(&message.into_bytes()).await?; + + let mut rx = { + let mut locked_room = room.lock().await; + + deny_invalid_user(&self_name, &locked_room)?; + locked_room.push(self_name.clone()); + + let rx = tx.subscribe(); + + let message = MessageType::UserJoined(self_name.clone()); + tx.send(message)?; + + rx + }; + + loop { + tokio::select! { + read_amount = read_to_buffer(&mut reader, &mut read_buffer) => { + match read_amount { + Ok(_) => (), + Err(_) => { + tx.send(MessageType::UserQuit(self_name.clone()))?; + { + let mut the_room = room.lock().await; + let idx = the_room.iter().position(|name| *name == self_name).unwrap(); + the_room.remove(idx); + } + return Ok(()); + } + }; + + let newline = match find_next_newline(&read_buffer) { + Some(idx) => idx, + None => continue, + }; + let before_newline = read_buffer.drain(0..=newline).collect(); + + let message = String::from_utf8(before_newline)?; + if !message.trim().is_empty() { + tx.send(MessageType::TextMessage(self_name.clone(), message.to_string()))?; + + } + } + received = rx.recv() => { + match received? { + MessageType::UserJoined(user_name) => { + if user_name == self_name { + continue; + } + let message = format!("* {user_name} has entered the room\n"); + writer.write_all(&message.into_bytes()).await?; + } + MessageType::UserQuit(user_name) => { + let message = format!("* {user_name} has left the room\n"); + writer.write_all(&message.into_bytes()).await?; + } + MessageType::TextMessage(user_name, the_message) => { + if user_name == self_name { + continue; + } + let message = format!("[{user_name}] {}\n", the_message.trim()); + writer.write_all(&message.into_bytes()).await?; + } + } + } + + }; + } +} + +fn find_next_newline(read_buffer: &[u8]) -> Option { + read_buffer.iter().position(|c| *c == b'\n') +} + +async fn read_to_buffer( + reader: &mut tokio::io::ReadHalf, + read_buffer: &mut Vec, +) -> anyhow::Result { + let mut read_amount = 0; + + while !read_buffer.contains(&b'\n') { + read_amount += reader.read_buf(read_buffer).await?; + if read_amount == 0 { + bail!("user disconnected"); + } + } + Ok(read_amount) +} + +fn deny_invalid_user(name: &str, room: &MutexGuard<'_, Vec>) -> anyhow::Result<()> { + if name.is_empty() + || !name.chars().all(|c| c.is_alphanumeric()) + || room.iter().any(|n| n == name) + { + bail!("user '{name}' is invalid or already exists"); + } + + Ok(()) +} + +async fn generate_room_contains_message(room: &Arc>>) -> String { + let the_room = room.lock().await; + let mut message = String::from("* The room contains: "); + let mut iter = the_room.iter().peekable(); + + while let Some(user) = iter.next() { + message.push_str(user.as_str()); + if iter.peek().is_some() { + message.push_str(", "); + } + } + + message.push('\n'); + message +} + +#[derive(Clone, Debug)] +enum MessageType { + UserJoined(String), + UserQuit(String), + TextMessage(String, String), // user, message +}