M1a: deterministischer Rust-Kern #3
@@ -0,0 +1,169 @@
|
|||||||
|
//! Minimal little-endian binary encoding for commands, snapshots and
|
||||||
|
//! recordings.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Error returned when bytes cannot be decoded.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum DecodeError {
|
||||||
|
/// The input ended before the value was complete.
|
||||||
|
UnexpectedEnd,
|
||||||
|
/// The input contained a value that is not allowed at this position.
|
||||||
|
InvalidValue(&'static str),
|
||||||
|
/// The input contained bytes after the last expected value.
|
||||||
|
TrailingBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DecodeError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DecodeError::UnexpectedEnd => write!(f, "unexpected end of input"),
|
||||||
|
DecodeError::InvalidValue(what) => write!(f, "invalid value: {what}"),
|
||||||
|
DecodeError::TrailingBytes => write!(f, "trailing bytes after the last value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for DecodeError {}
|
||||||
|
|
||||||
|
/// Appends values to a growing byte buffer.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Writer {
|
||||||
|
buf: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u8(&mut self, value: u8) {
|
||||||
|
self.buf.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u16(&mut self, value: u16) {
|
||||||
|
self.buf.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u32(&mut self, value: u32) {
|
||||||
|
self.buf.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u64(&mut self, value: u64) {
|
||||||
|
self.buf.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn i32(&mut self, value: i32) {
|
||||||
|
self.buf.extend_from_slice(&value.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool(&mut self, value: bool) {
|
||||||
|
self.buf.push(u8::from(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes bytes as they are, without a length.
|
||||||
|
pub fn raw(&mut self, value: &[u8]) {
|
||||||
|
self.buf.extend_from_slice(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a `u32` length followed by the bytes.
|
||||||
|
pub fn bytes(&mut self, value: &[u8]) {
|
||||||
|
let len = u32::try_from(value.len()).expect("length fits in u32");
|
||||||
|
self.u32(len);
|
||||||
|
self.buf.extend_from_slice(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a string as UTF-8 with a `u32` length in front.
|
||||||
|
pub fn str(&mut self, value: &str) {
|
||||||
|
self.bytes(value.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads values from a byte slice in the order they were written.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Reader<'a> {
|
||||||
|
buf: &'a [u8],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Reader<'a> {
|
||||||
|
pub fn new(buf: &'a [u8]) -> Self {
|
||||||
|
Self { buf, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of bytes that have not been read yet.
|
||||||
|
pub fn remaining(&self) -> usize {
|
||||||
|
self.buf.len() - self.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads exactly `len` bytes.
|
||||||
|
pub fn raw(&mut self, len: usize) -> Result<&'a [u8], DecodeError> {
|
||||||
|
if len > self.remaining() {
|
||||||
|
return Err(DecodeError::UnexpectedEnd);
|
||||||
|
}
|
||||||
|
let slice = &self.buf[self.pos..self.pos + len];
|
||||||
|
self.pos += len;
|
||||||
|
Ok(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array<const N: usize>(&mut self) -> Result<[u8; N], DecodeError> {
|
||||||
|
let mut out = [0u8; N];
|
||||||
|
out.copy_from_slice(self.raw(N)?);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u8(&mut self) -> Result<u8, DecodeError> {
|
||||||
|
Ok(self.array::<1>()?[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u16(&mut self) -> Result<u16, DecodeError> {
|
||||||
|
Ok(u16::from_le_bytes(self.array()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u32(&mut self) -> Result<u32, DecodeError> {
|
||||||
|
Ok(u32::from_le_bytes(self.array()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u64(&mut self) -> Result<u64, DecodeError> {
|
||||||
|
Ok(u64::from_le_bytes(self.array()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn i32(&mut self) -> Result<i32, DecodeError> {
|
||||||
|
Ok(i32::from_le_bytes(self.array()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool(&mut self) -> Result<bool, DecodeError> {
|
||||||
|
match self.u8()? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => Err(DecodeError::InvalidValue("bool")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a `u32` length followed by that many bytes.
|
||||||
|
pub fn bytes(&mut self) -> Result<&'a [u8], DecodeError> {
|
||||||
|
let len = self.u32()? as usize;
|
||||||
|
self.raw(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a UTF-8 string with a `u32` length in front.
|
||||||
|
pub fn string(&mut self) -> Result<String, DecodeError> {
|
||||||
|
let bytes = self.bytes()?;
|
||||||
|
std::str::from_utf8(bytes)
|
||||||
|
.map(str::to_owned)
|
||||||
|
.map_err(|_| DecodeError::InvalidValue("utf-8 string"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Succeeds only if every byte has been read.
|
||||||
|
pub fn finish(self) -> Result<(), DecodeError> {
|
||||||
|
if self.remaining() == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(DecodeError::TrailingBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,3 +5,5 @@
|
|||||||
//! only with whole numbers. Given the same commands it produces the same
|
//! only with whole numbers. Given the same commands it produces the same
|
||||||
//! world on every machine. That is what lets every player simulate the whole
|
//! world on every machine. That is what lets every player simulate the whole
|
||||||
//! world and exchange nothing but commands.
|
//! world and exchange nothing but commands.
|
||||||
|
|
||||||
|
pub mod codec;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use grovewright_core::codec::{DecodeError, Reader, Writer};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_kind_of_value_survives_a_round_trip() {
|
||||||
|
let mut w = Writer::new();
|
||||||
|
w.u8(0xAB);
|
||||||
|
w.u16(0xBEEF);
|
||||||
|
w.u32(0xDEAD_BEEF);
|
||||||
|
w.u64(0x0123_4567_89AB_CDEF);
|
||||||
|
w.i32(-42);
|
||||||
|
w.bool(true);
|
||||||
|
w.bool(false);
|
||||||
|
w.bytes(&[1, 2, 3]);
|
||||||
|
w.str("Grüner Hain");
|
||||||
|
w.raw(&[9, 9]);
|
||||||
|
let bytes = w.into_bytes();
|
||||||
|
|
||||||
|
let mut r = Reader::new(&bytes);
|
||||||
|
assert_eq!(r.u8(), Ok(0xAB));
|
||||||
|
assert_eq!(r.u16(), Ok(0xBEEF));
|
||||||
|
assert_eq!(r.u32(), Ok(0xDEAD_BEEF));
|
||||||
|
assert_eq!(r.u64(), Ok(0x0123_4567_89AB_CDEF));
|
||||||
|
assert_eq!(r.i32(), Ok(-42));
|
||||||
|
assert_eq!(r.bool(), Ok(true));
|
||||||
|
assert_eq!(r.bool(), Ok(false));
|
||||||
|
assert_eq!(r.bytes(), Ok(&[1u8, 2, 3][..]));
|
||||||
|
assert_eq!(r.string(), Ok("Grüner Hain".to_owned()));
|
||||||
|
assert_eq!(r.raw(2), Ok(&[9u8, 9][..]));
|
||||||
|
assert_eq!(r.finish(), Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn integers_are_little_endian() {
|
||||||
|
let mut w = Writer::new();
|
||||||
|
w.u32(0x0102_0304);
|
||||||
|
assert_eq!(w.into_bytes(), vec![4, 3, 2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reading_past_the_end_fails() {
|
||||||
|
let mut r = Reader::new(&[1, 2]);
|
||||||
|
assert_eq!(r.u32(), Err(DecodeError::UnexpectedEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_oversized_length_fails_without_panicking() {
|
||||||
|
let mut w = Writer::new();
|
||||||
|
w.u32(u32::MAX);
|
||||||
|
let bytes = w.into_bytes();
|
||||||
|
let mut r = Reader::new(&bytes);
|
||||||
|
assert_eq!(r.bytes(), Err(DecodeError::UnexpectedEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_bool_and_invalid_utf8_are_rejected() {
|
||||||
|
let mut r = Reader::new(&[2]);
|
||||||
|
assert_eq!(r.bool(), Err(DecodeError::InvalidValue("bool")));
|
||||||
|
|
||||||
|
let mut w = Writer::new();
|
||||||
|
w.bytes(&[0xFF, 0xFE]);
|
||||||
|
let bytes = w.into_bytes();
|
||||||
|
let mut r = Reader::new(&bytes);
|
||||||
|
assert_eq!(r.string(), Err(DecodeError::InvalidValue("utf-8 string")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finish_rejects_bytes_that_were_not_read() {
|
||||||
|
let r = Reader::new(&[0]);
|
||||||
|
assert_eq!(r.finish(), Err(DecodeError::TrailingBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remaining_counts_down_as_values_are_read() {
|
||||||
|
let mut r = Reader::new(&[1, 2, 3]);
|
||||||
|
assert_eq!(r.remaining(), 3);
|
||||||
|
r.u8().unwrap();
|
||||||
|
assert_eq!(r.remaining(), 2);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user