add problem 08
This commit is contained in:
@@ -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<Vec<u8>> {
|
||||
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<String> {
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
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<usize> {
|
||||
read_buffer.chars().position(|c| c == '\n')
|
||||
}
|
||||
|
||||
async fn read_to_buffer_and_decode(
|
||||
reader: &mut ReadHalf<'_>,
|
||||
encoded_read_buffer: &mut Vec<u8>,
|
||||
decoded_read_buffer: &mut String,
|
||||
client_stream_position: &mut usize,
|
||||
ciphers: &[Cipher],
|
||||
) -> Result<usize> {
|
||||
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<Vec<Cipher>> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user