/// Definitions for all the packets in the Minecraft protocol. pub mod packets; use crate::{mctypes::*, CONFIG, FAVICON}; use log::debug; use packets::*; use serde_json::json; use std::time::{Duration, Instant}; use tokio::net::TcpStream; /// The network client can only be in a few states, /// this enum keeps track of that. #[derive(PartialEq, Debug)] pub enum NetworkClientState { Handshake, Status, Login, Play, Disconnected, } /// A wrapper to contain everything related /// to networking for the client. #[derive(Debug)] pub struct NetworkClient { pub id: u128, pub connected: bool, pub stream: TcpStream, pub state: NetworkClientState, pub uuid: Option, pub username: Option, pub last_keep_alive: Instant, pub packets_read: usize, pub packets_sent: usize, } impl NetworkClient { /// Create a new `NetworkClient` pub fn new(stream: TcpStream, id: u128) -> NetworkClient { NetworkClient { id, connected: true, stream, state: NetworkClientState::Handshake, uuid: None, username: None, last_keep_alive: Instant::now(), packets_read: 0, packets_sent: 0, } } /// Update the client. /// /// Updating could mean connecting new clients, reading packets, /// writing packets, or disconnecting clients. pub async fn update(&mut self, num_players: usize) -> tokio::io::Result<()> { // println!("{:?}", self); match self.state { NetworkClientState::Handshake => { let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let handshake = self.get_packet::().await?; // Minecraft versions 1.8 - 1.8.9 use protocol version 47. let compatible_versions = handshake.protocol_version == 47; let next_state = match handshake.next_state.into() { 1 => NetworkClientState::Status, 2 => NetworkClientState::Login, _ => NetworkClientState::Disconnected, }; self.state = next_state; // If incompatible versions or wrong next state if !compatible_versions { let mut logindisconnect = LoginDisconnect::new(); logindisconnect.reason = MCChat { text: MCString::from("Incompatible client! Server is on 1.8.9"), }; self.send_packet(logindisconnect).await?; self.state = NetworkClientState::Disconnected; } } NetworkClientState::Status => { let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let _statusrequest = self.get_packet::().await?; let mut statusresponse = StatusResponse::new(); statusresponse.json_response = json!({ "version": { "name": "1.8.9", "protocol": 47, }, "players": { "max": CONFIG.max_players, "online": num_players, "sample": [ { "name": "shvr", "id": "e3f58380-60bb-4714-91f2-151d525e64aa" } ] }, "description": { "text": CONFIG.motd }, "favicon": format!("data:image/png;base64,{}", if FAVICON.is_ok() { radix64::STD.encode(FAVICON.as_ref().unwrap().as_slice()) } else { "".to_owned() }) }) .to_string() .into(); self.send_packet(statusresponse).await?; let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let statusping = self.get_packet::().await?; let mut statuspong = StatusPong::new(); statuspong.payload = statusping.payload; self.send_packet(statuspong).await?; self.state = NetworkClientState::Disconnected; } NetworkClientState::Login => { let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let loginstart = self.get_packet::().await?; // Offline mode skips encryption and compression. // TODO: Encryption and compression let mut loginsuccess = LoginSuccess::new(); // We're in offline mode, so this is a temporary uuid. // TODO: Get uuid and username from Mojang servers. loginsuccess.uuid = "00000000-0000-3000-0000-000000000000".into(); loginsuccess.username = loginstart.player_name; self.uuid = Some(loginsuccess.uuid.clone().into()); self.username = Some(loginsuccess.username.clone().into()); self.send_packet(loginsuccess).await?; self.state = NetworkClientState::Play; let joingame = JoinGame::new(); // TODO: Fill out `joingame` with actual information. self.send_packet(joingame).await?; let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let _clientsettings = self.get_packet::().await?; // TODO: Actually use client settings. let helditemchange = HeldItemChange::new(); // TODO: Retrieve selected slot from storage. self.send_packet(helditemchange).await?; // TODO: S->C Declare Recipes (1.16?) // TODO: S->C Tags (1.16?) // TODO: S->C Entity Status (optional?) // TODO: S->C Declare Commands (1.16?) // TODO: S->C Unlock Recipes (1.16?) // TODO: S->C Player Position and Look let playerpositionandlook = ClientboundPlayerPositionAndLook::new(); // TODO: Retrieve player position from storage. self.send_packet(playerpositionandlook).await?; // TODO: S->C Player Info (Add Player action) (1.16?) // TODO: S->C Player Info (Update latency action) (1.16?) // TODO: S->C Update View Position (1.16?) // TODO: S->C Update Light (1.16?) // TODO: S->C Chunk Data // TODO: S->C World Border // TODO: S->C Spawn Position let spawnposition = SpawnPosition::new(); self.send_packet(spawnposition).await?; // Send initial keep alive. self.send_chat_message("keep alive").await?; self.keep_alive().await?; // TODO: S->C Player Position and Look // TODO: C->S Teleport Confirm // TODO: C->S Player Position and Look // TODO: C->S Client Status // TODO: S->C inventories, entities, etc. self.send_chat_message(format!( "Welcome {} to the server!", self.username.as_ref().unwrap_or(&"unknown".to_owned()) )) .await?; } NetworkClientState::Play => { if self.last_keep_alive.elapsed() > Duration::from_millis(1000) { self.send_chat_message("keep alive").await?; self.keep_alive().await?; } let (packet_length, packet_id) = read_packet_header(&mut self.stream).await?; // debug!("{}", packet_id); if packet_id == Player::id() { let _player = self.get_packet::().await?; } else if packet_id == PlayerPosition::id() { let _playerposition = self.get_packet::().await?; } else if packet_id == PlayerLook::id() { let _playerlook = self.get_packet::().await?; } else if packet_id == ServerboundPlayerPositionAndLook::id() { let _playerpositionandlook = self .get_packet::() .await?; } else if packet_id == ServerboundChatMessage::id() { let serverboundchatmessage = self.get_packet::().await?; self.send_chat_message(format!( "<{}> {}", self.username.as_ref().unwrap_or(&"unknown".to_owned()), serverboundchatmessage.text )) .await?; } else { let _ = read_bytes(&mut self.stream, Into::::into(packet_length) as usize) .await?; } } NetworkClientState::Disconnected => { if self.connected { self.disconnect(None).await?; } } } Ok(()) } /// Send a generic packet to the client. pub async fn send_packet(&mut self, packet: P) -> tokio::io::Result<()> { self.packets_sent += 1; debug!("Sent {:?} {:#04X?} {:?}", self.state, P::id(), packet); packet.write(&mut self.stream).await } /// Read a generic packet from the network. pub async fn get_packet(&mut self) -> tokio::io::Result { self.packets_read += 1; let packet = T::read(&mut self.stream).await?; debug!("Got {:?} {:#04X?} {:?}", self.state, T::id(), packet); Ok(packet) } /// Send the client a message in chat. pub async fn send_chat_message>( &mut self, message: C, ) -> tokio::io::Result<()> { let mut chatmessage = ClientboundChatMessage::new(); chatmessage.text = message.into(); self.send_packet(chatmessage).await?; Ok(()) } /// Disconnect the client. /// /// Sends `0x40 Disconnect` then waits 10 seconds before forcing the connection closed. pub async fn disconnect(&mut self, reason: Option<&str>) -> tokio::io::Result<()> { let mut disconnect = Disconnect::new(); disconnect.reason.text = reason.unwrap_or("Disconnected").into(); self.send_packet(disconnect).await?; self.force_disconnect(); Ok(()) } /// Force disconnect the client by marking it for cleanup as disconnected. pub fn force_disconnect(&mut self) { self.connected = false; self.state = NetworkClientState::Disconnected; } /// Send a keep alive packet to the client. async fn keep_alive(&mut self) -> tokio::io::Result<()> { // Keep alive ping to client. let clientboundkeepalive = KeepAlivePing::new(); self.send_packet(clientboundkeepalive).await?; // Keep alive pong to server. let (_packet_length, _packet_id) = read_packet_header(&mut self.stream).await?; let _serverboundkeepalive = self.get_packet::().await?; self.last_keep_alive = Instant::now(); Ok(()) } }