/*
Hurry Curry! - a game about cooking
Copyright 2024 metamuffin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
use anyhow::Result;
use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION};
use log::{debug, info, warn};
use std::{collections::VecDeque, net::TcpStream};
use tungstenite::{
client::{uri_mode, IntoClientRequest},
client_tls_with_config,
handshake::client::Request,
stream::{MaybeTlsStream, Mode},
util::NonBlockingError,
Message, WebSocket,
};
pub struct Network {
sock: WebSocket>,
pub queue_in: VecDeque,
pub queue_out: VecDeque,
use_bincode: bool,
}
impl Network {
pub fn connect(addr: &str) -> Result {
let (parts, _) = addr.into_client_request()?.into_parts();
let mut builder = Request::builder()
.uri(parts.uri.clone().clone())
.method(parts.method.clone())
.version(parts.version);
*builder.headers_mut().unwrap() = parts.headers.clone();
let request = builder.body(())?;
let host = request.uri().host().unwrap_or("127.0.0.1");
let host = if host.starts_with('[') {
&host[1..host.len() - 1]
} else {
host
};
let port = request
.uri()
.port_u16()
.unwrap_or(match uri_mode(request.uri())? {
Mode::Plain => 27032,
Mode::Tls => 443,
});
info!("Connecting: host={host:?} port={port}");
let stream = TcpStream::connect((host, port))?;
stream.set_nodelay(true)?;
let (mut sock, _) = client_tls_with_config(request, stream, None, None)?;
match sock.get_mut() {
MaybeTlsStream::Plain(s) => s.set_nonblocking(true)?,
MaybeTlsStream::Rustls(s) => s.sock.set_nonblocking(true)?,
_ => todo!(),
};
info!("Handshake complete.");
Ok(Self {
sock,
use_bincode: false,
queue_in: VecDeque::new(),
queue_out: VecDeque::new(),
})
}
pub fn poll(&mut self) -> anyhow::Result<()> {
loop {
self.queue_in.extend(match self.sock.read() {
Ok(Message::Text(packet)) => match serde_json::from_str(&packet) {
Ok(packet) => {
debug!("<- {packet:?}");
if let PacketC::Version {
minor,
major,
supports_bincode,
} = &packet
{
if *minor == VERSION.0 && *major == VERSION.1 && *supports_bincode {
info!("Binary protocol format enabled.");
self.use_bincode = true;
}
}
Some(packet)
}
Err(e) => {
warn!("invalid json packet: {e:?}");
None
}
},
Ok(Message::Binary(packet)) => {
match bincode::decode_from_slice(&packet, BINCODE_CONFIG) {
Ok((packet, _)) => {
debug!("<- {packet:?}");
Some(packet)
}
Err(e) => {
warn!("invalid bincode packet: {e:?}");
None
}
}
}
Ok(_) => None,
Err(e) => {
if let Some(e) = e.into_non_blocking() {
warn!("{e:?}");
None
} else {
break;
}
}
});
}
for packet in self.queue_out.drain(..) {
debug!("-> {packet:?}");
if self.use_bincode {
self.sock.write(Message::Binary(
bincode::encode_to_vec(&packet, BINCODE_CONFIG)?.into(),
))?;
} else {
self.sock
.write(Message::Text(serde_json::to_string(&packet)?.into()))?;
}
}
self.sock.flush()?;
Ok(())
}
}