maelstrom-base 0.14.0

Fundamental data structures used by all Maelstrom code.
Documentation
use crate::WindowSize;
use std::mem;

pub fn encode_input(bytes: &[u8]) -> impl Iterator<Item = &[u8]> {
    bytes
        .split(|b| *b == 0xff)
        .enumerate()
        .flat_map(|(n, bytes)| {
            let mut vec = Vec::<&[u8]>::new();
            if n > 0 {
                vec.push(b"\xff\xff");
            }
            if !bytes.is_empty() {
                vec.push(bytes);
            }
            vec
        })
}

pub fn encode_window_size_change(window_size: WindowSize) -> [u8; 6] {
    let rows = window_size.rows.to_be_bytes();
    let columns = window_size.columns.to_be_bytes();
    [0xff, 0, rows[0], rows[1], columns[0], columns[1]]
}

pub trait DecodeInputAcceptor<E> {
    fn input(&mut self, input: &[u8]) -> Result<(), E>;
    fn window_size_change(&mut self, window_size: WindowSize) -> Result<(), E>;
}

#[derive(Debug, Default, PartialEq)]
pub struct DecodeInputRemainder {
    data: [u8; 5],
    length: u8,
}

impl DecodeInputRemainder {
    pub fn new(remainder: &[u8]) -> Self {
        let mut res: Self = Default::default();
        let length = remainder.len();
        assert!(length <= mem::size_of_val(&res.data));
        res.data[..length].copy_from_slice(remainder);
        res.length = length as u8;
        res
    }

    pub fn move_to_slice(&mut self, dest: &mut [u8]) -> usize {
        let length = self.len();
        dest[..length].copy_from_slice(&self.data[..length]);
        *self = Self::default();
        length
    }

    pub fn len(&self) -> usize {
        self.length.into()
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

#[derive(Debug, PartialEq)]
pub enum DecodeInputChunk<'a> {
    Input(&'a [u8]),
    WindowSizeChange(WindowSize),
    Remainder(DecodeInputRemainder),
}

pub struct DecodeInputIterator<'a>(&'a [u8]);

impl<'a> Iterator for DecodeInputIterator<'a> {
    type Item = DecodeInputChunk<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        match self.0 {
            [] => None,
            [0xff, 0xff, rest @ ..] => {
                let res = Some(DecodeInputChunk::Input(&self.0[1..2]));
                self.0 = rest;
                res
            }
            [0xff, 0, rh, rl, ch, cl, rest @ ..] => {
                let res = Some(DecodeInputChunk::WindowSizeChange(WindowSize::new(
                    u16::from_be_bytes([*rh, *rl]),
                    u16::from_be_bytes([*ch, *cl]),
                )));
                self.0 = rest;
                res
            }
            [0xff, 0, ..] | [0xff] => {
                let res = Some(DecodeInputChunk::Remainder(DecodeInputRemainder::new(
                    self.0,
                )));
                self.0 = b"";
                res
            }
            [0xff, _, rest @ ..] => {
                let res = Some(DecodeInputChunk::Input(&self.0[..2]));
                self.0 = rest;
                res
            }
            _ => {
                let next_0xff = self
                    .0
                    .iter()
                    .position(|b| *b == 0xff)
                    .unwrap_or(self.0.len());
                let res = Some(DecodeInputChunk::Input(&self.0[..next_0xff]));
                self.0 = &self.0[next_0xff..];
                res
            }
        }
    }
}

pub fn decode_input(input: &[u8]) -> DecodeInputIterator {
    DecodeInputIterator(input)
}

#[cfg(test)]
mod tests {
    use super::*;
    use DecodeInputChunk::*;

    fn assert_encode_input<const N: usize>(input: &[u8], expected: [&[u8]; N]) {
        assert_eq!(
            Vec::from_iter(encode_input(input)),
            Vec::from_iter(expected)
        );
    }

    #[test]
    fn encode_input_empty() {
        assert_encode_input(b"", []);
    }

    #[test]
    fn encode_input_basic() {
        assert_encode_input(b"abc", [b"abc"]);
    }

    #[test]
    fn encode_input_leading_escape() {
        assert_encode_input(b"\xffabcdef", [b"\xff\xff", b"abcdef"]);
    }

    #[test]
    fn encode_input_leading_escapes() {
        assert_encode_input(
            b"\xff\xff\xffabcdef",
            [b"\xff\xff", b"\xff\xff", b"\xff\xff", b"abcdef"],
        );
    }

    #[test]
    fn encode_input_only_escape() {
        assert_encode_input(b"\xff", [b"\xff\xff"]);
    }

    #[test]
    fn encode_input_only_escapes() {
        assert_encode_input(b"\xff\xff\xff", [b"\xff\xff", b"\xff\xff", b"\xff\xff"]);
    }

    #[test]
    fn encode_input_trailing_escape() {
        assert_encode_input(b"abcdef\xff", [b"abcdef", b"\xff\xff"]);
    }

    #[test]
    fn encode_input_trailing_escapes() {
        assert_encode_input(
            b"abcdef\xff\xff\xff",
            [b"abcdef", b"\xff\xff", b"\xff\xff", b"\xff\xff"],
        );
    }

    #[test]
    fn encode_input_middle_escape() {
        assert_encode_input(b"abc\xffdef", [b"abc", b"\xff\xff", b"def"]);
    }

    #[test]
    fn encode_input_middle_escapes() {
        assert_encode_input(
            b"abc\xff\xff\xffdef",
            [b"abc", b"\xff\xff", b"\xff\xff", b"\xff\xff", b"def"],
        );
    }

    #[test]
    fn encode_input_escapes() {
        assert_encode_input(
            b"\xffabc\xff\xff\xffdef\xff\xff",
            [
                b"\xff\xff",
                b"abc",
                b"\xff\xff",
                b"\xff\xff",
                b"\xff\xff",
                b"def",
                b"\xff\xff",
                b"\xff\xff",
            ],
        );
    }

    #[test]
    fn encode_window_size_change_basic() {
        assert_eq!(
            encode_window_size_change(WindowSize::new(0x89ab, 0xcdef)),
            *b"\xff\x00\x89\xab\xcd\xef"
        );
    }

    fn assert_decode_input<const N: usize>(input: &[u8], expected: [DecodeInputChunk; N]) {
        assert_eq!(
            Vec::from_iter(decode_input(input)),
            Vec::from_iter(expected),
        );
    }

    #[test]
    fn decode_input_empty() {
        assert_decode_input(b"", []);
    }

    #[test]
    fn decode_input_no_escapes() {
        assert_decode_input(b"abcdef", [Input(b"abcdef")]);
    }

    #[test]
    fn decode_input_escape_short_1_last() {
        assert_decode_input(
            b"abcdef\xff",
            [
                Input(b"abcdef"),
                Remainder(DecodeInputRemainder::new(b"\xff")),
            ],
        );
    }

    #[test]
    fn decode_input_escape_window_short_4_last() {
        assert_decode_input(
            b"abcdef\xff\x00",
            [
                Input(b"abcdef"),
                Remainder(DecodeInputRemainder::new(b"\xff\x00")),
            ],
        );
    }

    #[test]
    fn decode_input_escape_window_short_3_last() {
        assert_decode_input(
            b"abcdef\xff\x00\x89",
            [
                Input(b"abcdef"),
                Remainder(DecodeInputRemainder::new(b"\xff\x00\x89")),
            ],
        );
    }

    #[test]
    fn decode_input_escape_window_short_2_last() {
        assert_decode_input(
            b"abcdef\xff\x00\x89\xab",
            [
                Input(b"abcdef"),
                Remainder(DecodeInputRemainder::new(b"\xff\x00\x89\xab")),
            ],
        );
    }

    #[test]
    fn decode_input_escape_window_short_1_last() {
        assert_decode_input(
            b"abcdef\xff\x00\x89\xab\xcd",
            [
                Input(b"abcdef"),
                Remainder(DecodeInputRemainder::new(b"\xff\x00\x89\xab\xcd")),
            ],
        );
    }

    #[test]
    fn decode_input_escape_window_last() {
        assert_decode_input(
            b"abcdef\xff\x00\x89\xab\xcd\xef",
            [
                Input(b"abcdef"),
                WindowSizeChange(WindowSize::new(0x89ab, 0xcdef)),
            ],
        );
    }

    #[test]
    fn decode_input_escape_escape_last() {
        assert_decode_input(b"abcdef\xff\xff", [Input(b"abcdef"), Input(b"\xff")]);
    }

    #[test]
    fn decode_input_escape_garbage_last() {
        assert_decode_input(b"abcdef\xff\x01", [Input(b"abcdef"), Input(b"\xff\x01")]);
    }

    #[test]
    fn decode_input_kitchen_sink() {
        assert_decode_input(
            b"\xff\xff\x00\xff\x00\x89\xab\xcd\xff\xff\xff\xffabc",
            [
                Input(b"\xff"),
                Input(b"\x00"),
                WindowSizeChange(WindowSize::new(0x89ab, 0xcdff)),
                Input(b"\xff"),
                Input(b"\xffa"),
                Input(b"bc"),
            ],
        );
    }
}