neyuki 0.1.1

A small Lua-like scripting language implemented in Rust
// Raw binary buffer structure and memory manipulation

use std::convert::TryInto;

#[derive(Clone, Debug, PartialEq)]
pub struct VmBuffer {
    pub data: Vec<u8>,
}

impl VmBuffer {
    pub fn new(size: usize) -> Self {
        Self {
            data: vec![0; size],
        }
    }

    pub fn from_bytes(bytes: Vec<u8>) -> Self {
        Self { data: bytes }
    }

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

    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    fn check_bounds(&self, offset: usize, size: usize) -> Result<(), String> {
        match offset.checked_add(size) {
            Some(end) if end <= self.data.len() => Ok(()),
            _ => Err(format!(
                "buffer out of bounds: offset {} size {} buffer length {}",
                offset,
                size,
                self.data.len()
            )),
        }
    }

    pub fn read_u8(&self, offset: usize) -> Result<u8, String> {
        self.check_bounds(offset, 1)?;
        Ok(self.data[offset])
    }

    pub fn write_u8(&mut self, offset: usize, val: u8) -> Result<(), String> {
        self.check_bounds(offset, 1)?;
        self.data[offset] = val;
        Ok(())
    }

    pub fn read_i8(&self, offset: usize) -> Result<i8, String> {
        self.check_bounds(offset, 1)?;
        Ok(self.data[offset] as i8)
    }

    pub fn write_i8(&mut self, offset: usize, val: i8) -> Result<(), String> {
        self.check_bounds(offset, 1)?;
        self.data[offset] = val as u8;
        Ok(())
    }

    pub fn read_u16(&self, offset: usize) -> Result<u16, String> {
        self.check_bounds(offset, 2)?;
        let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().unwrap();
        Ok(u16::from_le_bytes(bytes))
    }

    pub fn write_u16(&mut self, offset: usize, val: u16) -> Result<(), String> {
        self.check_bounds(offset, 2)?;
        self.data[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_i16(&self, offset: usize) -> Result<i16, String> {
        self.check_bounds(offset, 2)?;
        let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().unwrap();
        Ok(i16::from_le_bytes(bytes))
    }

    pub fn write_i16(&mut self, offset: usize, val: i16) -> Result<(), String> {
        self.check_bounds(offset, 2)?;
        self.data[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_u32(&self, offset: usize) -> Result<u32, String> {
        self.check_bounds(offset, 4)?;
        let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().unwrap();
        Ok(u32::from_le_bytes(bytes))
    }

    pub fn write_u32(&mut self, offset: usize, val: u32) -> Result<(), String> {
        self.check_bounds(offset, 4)?;
        self.data[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_i32(&self, offset: usize) -> Result<i32, String> {
        self.check_bounds(offset, 4)?;
        let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().unwrap();
        Ok(i32::from_le_bytes(bytes))
    }

    pub fn write_i32(&mut self, offset: usize, val: i32) -> Result<(), String> {
        self.check_bounds(offset, 4)?;
        self.data[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_f32(&self, offset: usize) -> Result<f32, String> {
        self.check_bounds(offset, 4)?;
        let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().unwrap();
        Ok(f32::from_le_bytes(bytes))
    }

    pub fn write_f32(&mut self, offset: usize, val: f32) -> Result<(), String> {
        self.check_bounds(offset, 4)?;
        self.data[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_f64(&self, offset: usize) -> Result<f64, String> {
        self.check_bounds(offset, 8)?;
        let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().unwrap();
        Ok(f64::from_le_bytes(bytes))
    }

    pub fn write_f64(&mut self, offset: usize, val: f64) -> Result<(), String> {
        self.check_bounds(offset, 8)?;
        self.data[offset..offset + 8].copy_from_slice(&val.to_le_bytes());
        Ok(())
    }

    pub fn read_string(&self, offset: usize, count: usize) -> Result<String, String> {
        self.check_bounds(offset, count)?;
        String::from_utf8(self.data[offset..offset + count].to_vec())
            .map_err(|e| format!("invalid utf-8 string in buffer: {}", e))
    }

    pub fn write_string(&mut self, offset: usize, s: &str) -> Result<(), String> {
        let bytes = s.as_bytes();
        self.check_bounds(offset, bytes.len())?;
        self.data[offset..offset + bytes.len()].copy_from_slice(bytes);
        Ok(())
    }

    pub fn copy(
        &mut self,
        target_offset: usize,
        source: &VmBuffer,
        source_offset: usize,
        count: usize,
    ) -> Result<(), String> {
        self.check_bounds(target_offset, count)?;
        source.check_bounds(source_offset, count)?;
        self.data[target_offset..target_offset + count]
            .copy_from_slice(&source.data[source_offset..source_offset + count]);
        Ok(())
    }

    pub fn fill(&mut self, offset: usize, val: u8, count: usize) -> Result<(), String> {
        self.check_bounds(offset, count)?;
        for b in &mut self.data[offset..offset + count] {
            *b = val;
        }
        Ok(())
    }
}

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

    #[test]
    fn test_buffer_bounds_overflow_rejected() {
        let mut buf = VmBuffer::new(10);
        // usize::MAX + 1 wraps in unchecked arithmetic to 0, which would bypass offset + size > len
        let overflow_offset = usize::MAX;
        assert!(buf.read_u8(overflow_offset).is_err());
        assert!(buf.write_u8(overflow_offset, 42).is_err());
        assert!(buf.read_u16(overflow_offset).is_err());
        assert!(buf.write_u16(overflow_offset, 1234).is_err());
        assert!(buf.read_u32(overflow_offset).is_err());
        assert!(buf.write_u32(overflow_offset, 12345).is_err());
        assert!(buf.read_f64(overflow_offset).is_err());
        assert!(buf.write_f64(overflow_offset, 2.5).is_err());
    }
}