//! parse sending and receiving packets with a server. use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket}; use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket}; use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket}; use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket}; use crate::packets::ProtocolPacket; use crate::read::{read_packet, ReadPacketError}; use crate::write::write_packet; use crate::ServerIpAddress; use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc}; use bytes::BytesMut; use std::fmt::Debug; use std::marker::PhantomData; use thiserror::Error; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::TcpStream; pub struct ReadConnection { pub read_stream: OwnedReadHalf, buffer: BytesMut, pub compression_threshold: Option, pub dec_cipher: Option, _reading: PhantomData, } pub struct WriteConnection { pub write_stream: OwnedWriteHalf, pub compression_threshold: Option, pub enc_cipher: Option, _writing: PhantomData, } pub struct Connection { pub reader: ReadConnection, pub writer: WriteConnection, } impl ReadConnection where R: ProtocolPacket + Debug, { pub async fn read(&mut self) -> Result { read_packet::( &mut self.read_stream, &mut self.buffer, self.compression_threshold, &mut self.dec_cipher, ) .await } } impl WriteConnection where W: ProtocolPacket + Debug, { /// Write a packet to the server pub async fn write(&mut self, packet: W) -> std::io::Result<()> { write_packet( &packet, &mut self.write_stream, self.compression_threshold, &mut self.enc_cipher, ) .await } } impl Connection where R: ProtocolPacket + Debug, W: ProtocolPacket + Debug, { pub async fn read(&mut self) -> Result { self.reader.read().await } /// Write a packet to the server pub async fn write(&mut self, packet: W) -> std::io::Result<()> { self.writer.write(packet).await } /// Split the reader and writer into two objects. This doesn't allocate. pub fn into_split(self) -> (ReadConnection, WriteConnection) { (self.reader, self.writer) } } #[derive(Error, Debug)] pub enum ConnectionError { #[error("{0}")] Io(#[from] std::io::Error), } impl Connection { pub async fn new(address: &ServerIpAddress) -> Result { let ip = address.ip; let port = address.port; let stream = TcpStream::connect(format!("{}:{}", ip, port)).await?; // enable tcp_nodelay stream.set_nodelay(true)?; let (read_stream, write_stream) = stream.into_split(); Ok(Connection { reader: ReadConnection { read_stream, buffer: BytesMut::new(), compression_threshold: None, dec_cipher: None, _reading: PhantomData, }, writer: WriteConnection { write_stream, compression_threshold: None, enc_cipher: None, _writing: PhantomData, }, }) } pub fn login(self) -> Connection { Connection::from(self) } pub fn status(self) -> Connection { Connection::from(self) } } impl Connection { pub fn set_compression_threshold(&mut self, threshold: i32) { // if you pass a threshold of less than 0, compression is disabled if threshold >= 0 { self.reader.compression_threshold = Some(threshold as u32); self.writer.compression_threshold = Some(threshold as u32); } else { self.reader.compression_threshold = None; self.writer.compression_threshold = None; } } pub fn set_encryption_key(&mut self, key: [u8; 16]) { // minecraft has a cipher decoder and encoder, i don't think it matters though? let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key); self.writer.enc_cipher = Some(enc_cipher); self.reader.dec_cipher = Some(dec_cipher); } pub fn game(self) -> Connection { Connection::from(self) } } // rust doesn't let us implement From because allegedly it conflicts with // `core`'s "impl From for T" so we do this instead impl Connection where R1: ProtocolPacket + Debug, W1: ProtocolPacket + Debug, { fn from(connection: Connection) -> Connection where R2: ProtocolPacket + Debug, W2: ProtocolPacket + Debug, { Connection { reader: ReadConnection { read_stream: connection.reader.read_stream, buffer: connection.reader.buffer, compression_threshold: connection.reader.compression_threshold, dec_cipher: connection.reader.dec_cipher, _reading: PhantomData, }, writer: WriteConnection { compression_threshold: connection.writer.compression_threshold, write_stream: connection.writer.write_stream, enc_cipher: connection.writer.enc_cipher, _writing: PhantomData, }, } } }