use crate::errors::{EnderError, Result};
use tokio::io::{AsyncRead, AsyncReadExt};
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub fn encode_varint(value: i32) -> Vec<u8> {
let mut v = value as u32;
let mut buf = Vec::with_capacity(5);
loop {
let mut byte = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
byte |= 0x80;
}
buf.push(byte);
if v == 0 {
break;
}
}
buf
}
pub fn decode_varint(data: &[u8], offset: &mut usize) -> Result<i32> {
let mut result = 0i32;
let mut position = 0;
loop {
if *offset >= data.len() {
return Err(EnderError::PacketParse(
"Unexpected end of data while decoding VarInt".into(),
));
}
let byte = data[*offset];
*offset += 1;
result |= i32::from(byte & 0x7F) << position;
if byte & 0x80 == 0 {
return Ok(result);
}
position += 7;
if position >= 32 {
return Err(EnderError::PacketParse(
"VarInt too large (exceeds 32 bits)".into(),
));
}
}
}
pub async fn read_varint<R: AsyncRead + Unpin>(reader: &mut R) -> Result<i32> {
let mut result = 0i32;
let mut position = 0;
loop {
let mut buf = [0u8; 1];
reader.read_exact(&mut buf).await.map_err(EnderError::Io)?;
let byte = buf[0];
result |= i32::from(byte & 0x7F) << position;
if byte & 0x80 == 0 {
return Ok(result);
}
position += 7;
if position >= 32 {
return Err(EnderError::PacketParse(
"VarInt too large (exceeds 32 bits)".into(),
));
}
}
}
pub fn encode_mc_packet(payload: &[u8]) -> Result<Vec<u8>> {
let len = i32::try_from(payload.len())
.map_err(|_| EnderError::PacketParse("Minecraft packet payload exceeds i32::MAX".into()))?;
let mut packet = encode_varint(len);
packet.extend_from_slice(payload);
Ok(packet)
}
pub fn decode_string(data: &[u8], offset: &mut usize) -> Result<String> {
let len = usize::try_from(decode_varint(data, offset)?)
.map_err(|_| EnderError::PacketParse("Negative VarInt length for string".into()))?;
if *offset + len > data.len() {
return Err(EnderError::PacketParse("String length exceeds remaining data".into()));
}
let s = String::from_utf8(data[*offset..*offset + len].to_vec())
.map_err(|e| EnderError::PacketParse(format!("Invalid UTF-8 in string: {e}")))?;
*offset += len;
Ok(s)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn encode_varint_zero() {
assert_eq!(encode_varint(0), &[0x00]);
}
#[test]
fn encode_varint_single_byte() {
assert_eq!(encode_varint(127), &[0x7f]);
}
#[test]
fn encode_varint_two_bytes() {
assert_eq!(encode_varint(128), &[0x80, 0x01]);
}
#[test]
fn encode_varint_max() {
assert_eq!(encode_varint(-1), &[0xff, 0xff, 0xff, 0xff, 0x0f]);
}
#[test]
fn decode_varint_single_byte() {
let data = [0x7f];
let mut offset = 0;
assert_eq!(decode_varint(&data, &mut offset).unwrap(), 127);
assert_eq!(offset, 1);
}
#[test]
fn decode_varint_two_bytes() {
let data = [0x80, 0x01];
let mut offset = 0;
assert_eq!(decode_varint(&data, &mut offset).unwrap(), 128);
assert_eq!(offset, 2);
}
#[test]
fn decode_varint_negative() {
let data = [0xff, 0xff, 0xff, 0xff, 0x0f];
let mut offset = 0;
assert_eq!(decode_varint(&data, &mut offset).unwrap(), -1);
assert_eq!(offset, 5);
}
#[test]
fn decode_varint_truncated() {
let data = [0x80];
let mut offset = 0;
assert!(decode_varint(&data, &mut offset).is_err());
}
#[test]
fn encode_mc_packet_roundtrip() {
let payload = b"hello";
let packet = encode_mc_packet(payload).unwrap();
assert_eq!(packet, &[0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f]);
}
#[test]
fn decode_string_valid() {
let data = [0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f];
let mut offset = 0;
assert_eq!(decode_string(&data, &mut offset).unwrap(), "hello");
assert_eq!(offset, 6);
}
#[test]
fn decode_string_truncated() {
let data = [0x05, 0x68, 0x65];
let mut offset = 0;
assert!(decode_string(&data, &mut offset).is_err());
}
}