use std::io::Cursor;
use bytes::Buf;
use mc_varint::{VarInt, VarIntRead};
use snafu::{Backtrace, OptionExt, Snafu};
use tracing::trace;
use crate::mc_string::{decode_mc_string, McStringError};
#[derive(Snafu, Debug)]
pub enum FrameError {
Incomplete { backtrace: Backtrace },
#[snafu(display("I/O error: {source}"), context(false))]
Io {
source: std::io::Error,
backtrace: Backtrace,
},
InvalidLength { backtrace: Backtrace },
InvalidFrameId { id: i32, backtrace: Backtrace },
#[snafu(display("Failed to decode string: {source}"), context(false))]
StringDecodeFailed {
#[snafu(backtrace)]
source: McStringError,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Frame {
Handshake {
protocol: VarInt,
address: String,
port: u16,
state: VarInt,
},
StatusRequest,
StatusResponse {
json: String,
},
PingRequest {
payload: i64,
},
PingResponse {
payload: i64,
},
}
impl Frame {
pub const PROTOCOL_VERSION: i32 = 767;
pub const HANDSHAKE_ID: i32 = 0x00;
pub const STATUS_REQUEST_ID: i32 = 0x00;
pub const STATUS_RESPONSE_ID: i32 = 0x00;
pub const PING_REQUEST_ID: i32 = 0x01;
pub const PING_RESPONSE_ID: i32 = 0x01;
pub fn check(buf: &mut Cursor<&[u8]>) -> Result<(), FrameError> {
let available_data = buf.get_ref().len();
let remaining_data_len: usize =
i32::from(buf.read_var_int().ok().context(IncompleteSnafu)?)
.try_into()
.ok()
.context(InvalidLengthSnafu)?;
let header_len = buf.position() as usize;
let total_len = header_len + remaining_data_len;
let is_valid = available_data >= total_len;
if is_valid {
trace!("Valid frame, packet size: {total_len}, header size: {header_len}, body size: {remaining_data_len}, downloaded: {available_data}");
Ok(())
} else {
trace!("Invalid frame, packet size: {total_len}, downloaded: {available_data}");
IncompleteSnafu.fail()
}
}
pub fn parse(cursor: &mut Cursor<&[u8]>) -> Result<Frame, FrameError> {
let id = i32::from(cursor.read_var_int()?);
match id {
Self::STATUS_RESPONSE_ID => {
let json = decode_mc_string(cursor)?;
Ok(Frame::StatusResponse { json })
}
Self::PING_RESPONSE_ID => {
let payload = cursor.get_i64();
Ok(Frame::PingResponse { payload })
}
_ => InvalidFrameIdSnafu { id }.fail(),
}
}
}