use std::io::{self, Read, Write};
use mcproto_codec::error::{
CodecError, CodecErrorKind, CodecKind, CodecOperation, InvalidEncodingReason,
};
use mcproto_types::{
TypeCodec,
basic::{
Boolean, Byte, Float, Int, Long, LpVec3, Short, UnsignedByte, UnsignedShort, VarInt,
VarLong,
},
};
macro_rules! codec_case {
($name:ident, $type:ident, $value:expr, [$($byte:expr),+ $(,)?]) => {
#[test]
fn $name() {
let expected = [$($byte),+];
let mut encoded = Vec::new();
$type($value).encode(&mut encoded).unwrap();
assert_eq!(encoded, expected);
let mut input = expected.as_slice();
assert_eq!($type::decode(&mut input).unwrap(), $type($value));
assert!(input.is_empty());
}
};
}
codec_case!(boolean_false, Boolean, false, [0x00]);
codec_case!(boolean_true, Boolean, true, [0x01]);
codec_case!(byte_min, Byte, i8::MIN, [0x80]);
codec_case!(byte_negative_one, Byte, -1, [0xff]);
codec_case!(byte_zero, Byte, 0, [0x00]);
codec_case!(byte_max, Byte, i8::MAX, [0x7f]);
codec_case!(unsigned_byte_zero, UnsignedByte, 0, [0x00]);
codec_case!(unsigned_byte_example, UnsignedByte, 0xab, [0xab]);
codec_case!(unsigned_byte_max, UnsignedByte, u8::MAX, [0xff]);
codec_case!(short_min, Short, i16::MIN, [0x80, 0x00]);
codec_case!(short_negative_one, Short, -1, [0xff, 0xff]);
codec_case!(short_zero, Short, 0, [0x00, 0x00]);
codec_case!(short_example, Short, 0x1234, [0x12, 0x34]);
codec_case!(short_max, Short, i16::MAX, [0x7f, 0xff]);
codec_case!(unsigned_short_zero, UnsignedShort, 0, [0x00, 0x00]);
codec_case!(unsigned_short_example, UnsignedShort, 0xabcd, [0xab, 0xcd]);
codec_case!(unsigned_short_max, UnsignedShort, u16::MAX, [0xff, 0xff]);
codec_case!(int_min, Int, i32::MIN, [0x80, 0x00, 0x00, 0x00]);
codec_case!(int_negative_one, Int, -1, [0xff, 0xff, 0xff, 0xff]);
codec_case!(int_zero, Int, 0, [0x00, 0x00, 0x00, 0x00]);
codec_case!(int_example, Int, 0x1234_5678, [0x12, 0x34, 0x56, 0x78]);
codec_case!(int_max, Int, i32::MAX, [0x7f, 0xff, 0xff, 0xff]);
codec_case!(
long_min,
Long,
i64::MIN,
[0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
);
codec_case!(
long_negative_one,
Long,
-1,
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
);
codec_case!(
long_zero,
Long,
0,
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
);
codec_case!(float_one_and_a_half, Float, 1.5, [0x3f, 0xc0, 0x00, 0x00]);
codec_case!(
long_example,
Long,
0x0123_4567_89ab_cdef,
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]
);
codec_case!(
long_max,
Long,
i64::MAX,
[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
);
codec_case!(varint_zero, VarInt, 0, [0x00]);
codec_case!(varint_one, VarInt, 1, [0x01]);
codec_case!(varint_two_byte_min, VarInt, 128, [0x80, 0x01]);
codec_case!(varint_minecraft_port, VarInt, 25565, [0xdd, 0xc7, 0x01]);
codec_case!(
varint_negative_one,
VarInt,
-1,
[0xff, 0xff, 0xff, 0xff, 0x0f]
);
codec_case!(
varint_i32_min,
VarInt,
i32::MIN,
[0x80, 0x80, 0x80, 0x80, 0x08]
);
codec_case!(varlong_zero, VarLong, 0, [0x00]);
codec_case!(varlong_one, VarLong, 1, [0x01]);
codec_case!(varlong_two_byte_min, VarLong, 128, [0x80, 0x01]);
codec_case!(
varlong_i64_max,
VarLong,
i64::MAX,
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]
);
codec_case!(
varlong_negative_one,
VarLong,
-1,
[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]
);
codec_case!(
varlong_i64_min,
VarLong,
i64::MIN,
[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]
);
#[test]
fn lp_vec3_matches_protocol_samples() {
let cases: &[(LpVec3, &[u8])] = &[
(LpVec3::new(0.0, 0.0, 0.0), &[0x00]),
(
LpVec3::new(1.0, 0.0, -1.0),
&[0xf1, 0xff, 0x00, 0x00, 0xff, 0xff],
),
(
LpVec3::new(10.0, 0.2, -5.0),
&[0xf6, 0xff, 0x40, 0x01, 0x05, 0x1f, 0x02],
),
(
LpVec3::new(123457.0, 15.071, 0.0),
&[0xf5, 0xff, 0x7f, 0xff, 0x00, 0x07, 0x90, 0xf1, 0x01],
),
];
for (value, expected) in cases {
let mut encoded = Vec::new();
value.encode(&mut encoded).unwrap();
assert_eq!(&encoded, expected);
let mut input = *expected;
let decoded = LpVec3::decode(&mut input).unwrap();
let tolerance = value.x.abs().max(value.y.abs()).max(value.z.abs()).ceil()
/ LpVec3::MAX_QUANTIZED_VALUE
+ 1e-12;
assert!((decoded.x - value.x).abs() <= tolerance);
assert!((decoded.y - value.y).abs() <= tolerance);
assert!((decoded.z - value.z).abs() <= tolerance);
assert!(input.is_empty());
}
}
#[test]
fn lp_vec3_zero_form_consumes_only_one_byte() {
let mut input = [0x00, 0xaa].as_slice();
assert_eq!(LpVec3::decode(&mut input).unwrap(), LpVec3::default());
assert_eq!(input, [0xaa]);
}
#[test]
fn lp_vec3_tiny_and_nan_values_use_zero_form() {
let values = [
LpVec3::new(LpVec3::ZERO_THRESHOLD / 2.0, 0.0, 0.0),
LpVec3::new(f64::NAN, 1.0, -1.0),
];
for value in values {
let mut encoded = Vec::new();
value.encode(&mut encoded).unwrap();
assert_eq!(encoded, [0x00]);
}
}
#[test]
fn lp_vec3_rejects_unrepresentable_scale() {
let value = LpVec3::new(LpVec3::MAX_SCALE_FACTOR as f64 + 1.0, 0.0, 0.0);
let mut encoded = Vec::new();
let error = value.encode(&mut encoded).unwrap_err();
assert!(encoded.is_empty());
assert_eq!(
error.kind(),
CodecErrorKind::InvalidEncoding(InvalidEncodingReason::LpVec3ScaleOutOfRange {
scale_factor: LpVec3::MAX_SCALE_FACTOR + 1,
max: LpVec3::MAX_SCALE_FACTOR,
})
);
assert_eq!(error.codec(), CodecKind::LpVec3);
assert_eq!(error.operation(), CodecOperation::Write);
assert_eq!(error.bytes_processed(), 0);
}
#[test]
fn lp_vec3_truncated_payload_reports_exact_progress() {
let mut input = [0x01, 0x02, 0x03].as_slice();
let error = LpVec3::decode(&mut input).unwrap_err();
assert_eq!(error.kind(), CodecErrorKind::UnexpectedEof);
assert_eq!(error.codec(), CodecKind::LpVec3);
assert_eq!(error.operation(), CodecOperation::Read);
assert_eq!(error.bytes_processed(), 3);
}
#[test]
fn lp_vec3_truncated_continuation_keeps_varint_error() {
let mut input = [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80].as_slice();
let error = LpVec3::decode(&mut input).unwrap_err();
assert_eq!(error.kind(), CodecErrorKind::UnexpectedEof);
assert_eq!(error.codec(), CodecKind::VarInt);
assert_eq!(error.contexts(), &[CodecKind::LpVec3]);
assert_eq!(error.bytes_processed(), 1);
}
#[test]
fn boolean_rejects_non_boolean_values() {
for value in [0x02, 0x7f, 0xff] {
let encoded = [value];
let mut input = encoded.as_slice();
let error = Boolean::decode(&mut input).unwrap_err();
assert_eq!(
error.kind(),
CodecErrorKind::InvalidEncoding(InvalidEncodingReason::InvalidBooleanValue { value })
);
assert_eq!(error.codec(), CodecKind::Boolean);
assert_eq!(error.operation(), CodecOperation::Read);
assert_eq!(error.bytes_processed(), 1);
assert!(error.io_error().is_none());
}
}
#[test]
fn decoding_consumes_only_the_value_bytes() {
let mut input = [0x12, 0x34, 0x56, 0x78, 0xaa].as_slice();
assert_eq!(Int::decode(&mut input).unwrap(), Int(0x1234_5678));
assert_eq!(input, [0xaa]);
}
fn assert_error(
error: &CodecError,
error_kind: CodecErrorKind,
codec_kind: CodecKind,
operation: CodecOperation,
bytes_processed: usize,
) {
assert_eq!(error.kind(), error_kind);
assert_eq!(error.codec(), codec_kind);
assert_eq!(error.operation(), operation);
assert_eq!(error.bytes_processed(), bytes_processed);
assert!(error.io_error().is_some());
}
macro_rules! eof_case {
($name:ident, $type:ident, $kind:ident, [$($byte:expr),* $(,)?], $processed:expr) => {
#[test]
fn $name() {
let mut input = [$($byte),*].as_slice();
let error = $type::decode(&mut input).unwrap_err();
assert_error(
&error,
CodecErrorKind::UnexpectedEof,
CodecKind::$kind,
CodecOperation::Read,
$processed,
);
assert_eq!(
error.io_error().unwrap().kind(),
io::ErrorKind::UnexpectedEof
);
}
};
}
eof_case!(boolean_eof, Boolean, Boolean, [], 0);
eof_case!(byte_eof, Byte, Byte, [], 0);
eof_case!(unsigned_byte_eof, UnsignedByte, UnsignedByte, [], 0);
eof_case!(short_eof, Short, Short, [0x00], 1);
eof_case!(unsigned_short_eof, UnsignedShort, UnsignedShort, [0x00], 1);
eof_case!(int_eof, Int, Int, [0x00, 0x00, 0x00], 3);
eof_case!(
long_eof,
Long,
Long,
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
7
);
eof_case!(varint_eof, VarInt, VarInt, [], 0);
eof_case!(varlong_eof, VarLong, VarLong, [], 0);
eof_case!(float_eof, Float, Float, [0x3f, 0xc0], 2);
struct FailingReader;
impl Read for FailingReader {
fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
Err(io::Error::other("read failed"))
}
}
macro_rules! read_error_case {
($name:ident, $type:ident, $kind:ident) => {
#[test]
fn $name() {
let error = $type::decode(&mut FailingReader).unwrap_err();
assert_error(
&error,
CodecErrorKind::Io,
CodecKind::$kind,
CodecOperation::Read,
0,
);
assert_eq!(error.io_error().unwrap().kind(), io::ErrorKind::Other);
}
};
}
read_error_case!(boolean_read_error, Boolean, Boolean);
read_error_case!(byte_read_error, Byte, Byte);
read_error_case!(unsigned_byte_read_error, UnsignedByte, UnsignedByte);
read_error_case!(short_read_error, Short, Short);
read_error_case!(unsigned_short_read_error, UnsignedShort, UnsignedShort);
read_error_case!(int_read_error, Int, Int);
read_error_case!(long_read_error, Long, Long);
read_error_case!(varint_read_error, VarInt, VarInt);
read_error_case!(varlong_read_error, VarLong, VarLong);
read_error_case!(float_read_error, Float, Float);
read_error_case!(lp_vec3_read_error, LpVec3, LpVec3);
struct FailingWriter;
impl Write for FailingWriter {
fn write(&mut self, _buffer: &[u8]) -> io::Result<usize> {
Err(io::Error::other("write failed"))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
macro_rules! write_error_case {
($name:ident, $value:expr, $kind:ident) => {
#[test]
fn $name() {
let error = $value.encode(&mut FailingWriter).unwrap_err();
assert_error(
&error,
CodecErrorKind::Io,
CodecKind::$kind,
CodecOperation::Write,
0,
);
assert_eq!(error.io_error().unwrap().kind(), io::ErrorKind::Other);
}
};
}
write_error_case!(boolean_write_error, Boolean(false), Boolean);
write_error_case!(byte_write_error, Byte(0), Byte);
write_error_case!(unsigned_byte_write_error, UnsignedByte(0), UnsignedByte);
write_error_case!(short_write_error, Short(0), Short);
write_error_case!(unsigned_short_write_error, UnsignedShort(0), UnsignedShort);
write_error_case!(int_write_error, Int(0), Int);
write_error_case!(long_write_error, Long(0), Long);
write_error_case!(varint_write_error, VarInt(0), VarInt);
write_error_case!(varlong_write_error, VarLong(0), VarLong);
write_error_case!(float_write_error, Float(0.0), Float);
write_error_case!(lp_vec3_write_error, LpVec3::default(), LpVec3);
struct PartialErrorReader {
bytes: &'static [u8],
}
impl Read for PartialErrorReader {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if self.bytes.is_empty() {
return Err(io::Error::other("read failed after partial input"));
}
let read = buffer.len().min(self.bytes.len());
buffer[..read].copy_from_slice(&self.bytes[..read]);
self.bytes = &self.bytes[read..];
Ok(read)
}
}
#[test]
fn partial_read_error_reports_exact_progress() {
let mut reader = PartialErrorReader {
bytes: &[0x12, 0x34],
};
let error = Int::decode(&mut reader).unwrap_err();
assert_error(
&error,
CodecErrorKind::Io,
CodecKind::Int,
CodecOperation::Read,
2,
);
}
struct PartialErrorWriter {
remaining: usize,
}
impl Write for PartialErrorWriter {
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
if self.remaining == 0 {
return Err(io::Error::other("write failed after partial output"));
}
let written = buffer.len().min(self.remaining);
self.remaining -= written;
Ok(written)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn partial_write_error_reports_exact_progress() {
let mut writer = PartialErrorWriter { remaining: 3 };
let error = Long(0).encode(&mut writer).unwrap_err();
assert_error(
&error,
CodecErrorKind::Io,
CodecKind::Long,
CodecOperation::Write,
3,
);
}