diff --git a/problem08/Cargo.lock b/problem08/Cargo.lock new file mode 100644 index 0000000..fa66617 --- /dev/null +++ b/problem08/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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +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 = "problem08" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +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/problem08/Cargo.toml b/problem08/Cargo.toml new file mode 100644 index 0000000..f76dd86 --- /dev/null +++ b/problem08/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "problem08" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1", features = ["full"] } +anyhow = "1" diff --git a/problem08/src/main.rs b/problem08/src/main.rs new file mode 100644 index 0000000..cac0917 --- /dev/null +++ b/problem08/src/main.rs @@ -0,0 +1,242 @@ +use anyhow::{Context, Result, bail, ensure}; + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream, tcp::ReadHalf}, +}; + +const LOREM: &str = "Lorem ipsum dolor sit amet."; + +#[tokio::main] +async fn main() -> Result<()> { + let listener = TcpListener::bind("0.0.0.0:10000").await?; + + loop { + let (socket, _) = listener.accept().await?; + tokio::spawn(async move { + match handle_connection(socket).await { + Ok(()) => (), + Err(e) => { + dbg!(e); + } + } + }); + } +} + +async fn handle_connection(mut socket: TcpStream) -> Result<()> { + let (mut reader, mut writer) = socket.split(); + + let cipher_spec = receive_cipher_spec(&mut reader).await?; + + if is_no_op(&cipher_spec) { + socket.shutdown().await?; + return Ok(()); + } + + let mut client_stream_position = 0; + let mut server_stream_position = 0; + + let mut encoded_read_buffer = Vec::with_capacity(5_000); + let mut decoded_read_buffer = String::with_capacity(5_000); + + loop { + read_to_buffer_and_decode( + &mut reader, + &mut encoded_read_buffer, + &mut decoded_read_buffer, + &mut client_stream_position, + &cipher_spec, + ) + .await?; + + ensure!( + decoded_read_buffer.is_ascii(), + "error on decryption, non-ascii detected" + ); + + 'inner: loop { + let reply = match generate_reply_and_encode( + &mut decoded_read_buffer, + &mut server_stream_position, + &cipher_spec, + ) { + Ok(reply) => reply, + Err(_) => { + break 'inner; + } + }; + writer.write_all(&reply).await?; + } + } +} + +fn generate_reply_and_encode( + read_buffer: &mut String, + server_stream_position: &mut usize, + ciphers: &[Cipher], +) -> Result> { + let newline = match find_next_newline(read_buffer) { + Some(idx) => idx, + None => bail!("breaking inner"), + }; + + let binding = read_buffer.drain(0..=newline); + let this_line = binding.as_str(); + + let reply = generate_cleartext_reply(this_line)?; + + let encoded = encode(&reply, server_stream_position, ciphers); + + Ok(encoded) +} + +fn generate_cleartext_reply(request: &str) -> Result { + let toys = request.split(','); + + let mut itemized_toys = vec![]; + + for toy in toys { + let (amount, item) = toy.split_once('x').expect("no empty items"); + let amount: u32 = amount.parse().expect("only valid numbers"); + itemized_toys.push((amount, item)); + } + + itemized_toys.sort_by_key(|(amount, _)| *amount); + + let &(amount_requested, item_requested) = + itemized_toys.last().context("empty requests are invalid")?; + + let mut reply = String::new(); + reply.push_str(&amount_requested.to_string()); + reply.push_str("x "); + reply.push_str(item_requested.trim()); + reply.push('\n'); + + Ok(reply) +} + +fn encode(reply: &str, server_stream_position: &mut usize, ciphers: &[Cipher]) -> Vec { + let mut result = vec![]; + + for ch in reply.chars() { + let mut b = ch as u8; + for cipher in ciphers { + b = cipher.encode_byte(b, *server_stream_position); + } + result.push(b); + *server_stream_position += 1; + } + + result +} + +fn decode( + incoming_message: &mut Vec, + decoded_read_buffer: &mut String, + client_stream_position: &mut usize, + ciphers: &[Cipher], +) { + for byte in incoming_message.iter() { + let mut b = *byte; + for cipher in ciphers.iter().rev() { + b = cipher.decode_byte(b, *client_stream_position); + } + decoded_read_buffer.push(b as char); + *client_stream_position += 1; + } + incoming_message.clear(); +} + +fn find_next_newline(read_buffer: &str) -> Option { + read_buffer.chars().position(|c| c == '\n') +} + +async fn read_to_buffer_and_decode( + reader: &mut ReadHalf<'_>, + encoded_read_buffer: &mut Vec, + decoded_read_buffer: &mut String, + client_stream_position: &mut usize, + ciphers: &[Cipher], +) -> Result { + let mut read_amount = 0; + + while !decoded_read_buffer.contains('\n') { + read_amount += reader.read_buf(encoded_read_buffer).await?; + if read_amount == 0 { + bail!("client disconnected"); + } + decode( + encoded_read_buffer, + decoded_read_buffer, + client_stream_position, + ciphers, + ); + } + + Ok(read_amount) +} + +async fn receive_cipher_spec(reader: &mut ReadHalf<'_>) -> Result> { + let mut cipher_spec = vec![]; + + loop { + let byte = reader.read_u8().await?; + + match byte { + 0x00 => { + break; + } + 0x01 => cipher_spec.push(Cipher::ReverseBits), + 0x02 => { + let byte = reader.read_u8().await?; + cipher_spec.push(Cipher::Xor(byte)); + } + 0x03 => cipher_spec.push(Cipher::XorPos), + 0x04 => { + let byte = reader.read_u8().await?; + cipher_spec.push(Cipher::Add(byte)); + } + 0x05 => cipher_spec.push(Cipher::AddPos), + byte => bail!("illegal cipher request: {byte:x}"), + } + } + Ok(cipher_spec) +} + +fn is_no_op(ciphers: &[Cipher]) -> bool { + let clear = LOREM.as_bytes(); + let encoded = encode(LOREM, &mut 0, ciphers); + clear == encoded +} + +#[derive(Debug)] +enum Cipher { + ReverseBits, + Xor(u8), + XorPos, + Add(u8), + AddPos, +} + +impl Cipher { + fn decode_byte(&self, byte: u8, stream_position: usize) -> u8 { + match self { + Cipher::ReverseBits => byte.reverse_bits(), + Cipher::Xor(bits) => byte ^ bits, + Cipher::XorPos => byte ^ stream_position as u8, + Cipher::Add(summand) => byte.wrapping_sub(*summand), + Cipher::AddPos => byte.wrapping_sub(stream_position as u8), + } + } + + fn encode_byte(&self, byte: u8, stream_position: usize) -> u8 { + match self { + Cipher::ReverseBits => byte.reverse_bits(), + Cipher::Xor(bits) => byte ^ bits, + Cipher::XorPos => byte ^ stream_position as u8, + Cipher::Add(summand) => byte.wrapping_add(*summand), + Cipher::AddPos => byte.wrapping_add(stream_position as u8), + } + } +}