use std::sync::{Arc, RwLock, RwLockReadGuard};
use crate::emulation::engine::EmulationError;
pub const PAGE_SIZE: usize = 4096;
#[derive(Debug)]
pub struct Page {
backing: Arc<[u8; PAGE_SIZE]>,
local: RwLock<Option<Box<[u8; PAGE_SIZE]>>>,
}
impl Page {
#[must_use]
pub fn new(data: [u8; PAGE_SIZE]) -> Self {
Self {
backing: Arc::new(data),
local: RwLock::new(None),
}
}
#[must_use]
pub fn zeroed() -> Self {
Self::new([0u8; PAGE_SIZE])
}
#[must_use]
pub fn from_slice(data: &[u8]) -> Self {
let mut page_data = [0u8; PAGE_SIZE];
let copy_len = data.len().min(PAGE_SIZE);
page_data[..copy_len].copy_from_slice(&data[..copy_len]);
Self::new(page_data)
}
pub fn read_byte(&self, offset: usize) -> Result<u8, EmulationError> {
if offset >= PAGE_SIZE {
return Err(EmulationError::PageOutOfBounds {
offset,
size: 1,
page_size: PAGE_SIZE,
});
}
let local = self
.local
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
Ok(local
.as_ref()
.map_or(self.backing[offset], |data| data[offset]))
}
pub fn read(&self, offset: usize, buf: &mut [u8]) -> Result<(), EmulationError> {
let end = offset.saturating_add(buf.len());
if end > PAGE_SIZE || end < offset {
return Err(EmulationError::PageOutOfBounds {
offset,
size: buf.len(),
page_size: PAGE_SIZE,
});
}
let local = self
.local
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
let src = local
.as_ref()
.map_or(&self.backing[offset..end], |data| &data[offset..end]);
buf.copy_from_slice(src);
Ok(())
}
pub fn read_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>, EmulationError> {
let mut buf = vec![0u8; len];
self.read(offset, &mut buf)?;
Ok(buf)
}
pub fn write_byte(&self, offset: usize, value: u8) -> Result<(), EmulationError> {
if offset >= PAGE_SIZE {
return Err(EmulationError::PageOutOfBounds {
offset,
size: 1,
page_size: PAGE_SIZE,
});
}
let mut local = self
.local
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
let buf = local.get_or_insert_with(|| Box::new(*self.backing));
buf[offset] = value;
Ok(())
}
pub fn write(&self, offset: usize, data: &[u8]) -> Result<(), EmulationError> {
let end = offset.saturating_add(data.len());
if end > PAGE_SIZE || end < offset {
return Err(EmulationError::PageOutOfBounds {
offset,
size: data.len(),
page_size: PAGE_SIZE,
});
}
let mut local = self
.local
.write()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
let buf = local.get_or_insert_with(|| Box::new(*self.backing));
buf[offset..end].copy_from_slice(data);
Ok(())
}
pub fn is_modified(&self) -> Result<bool, EmulationError> {
let local = self
.local
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
Ok(local.is_some())
}
pub fn fork(&self) -> Result<Self, EmulationError> {
let local = self
.local
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
let new_backing = local.as_ref().map_or_else(
|| Arc::clone(&self.backing), |data| Arc::new(**data), );
Ok(Self {
backing: new_backing,
local: RwLock::new(None),
})
}
pub fn data(&self) -> Result<PageDataGuard<'_>, EmulationError> {
let local = self
.local
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "page local buffer",
})?;
Ok(PageDataGuard { page: self, local })
}
}
impl Clone for Page {
fn clone(&self) -> Self {
self.fork().expect("page lock poisoned during clone")
}
}
impl Default for Page {
fn default() -> Self {
Self::zeroed()
}
}
pub struct PageDataGuard<'a> {
page: &'a Page,
local: RwLockReadGuard<'a, Option<Box<[u8; PAGE_SIZE]>>>,
}
impl PageDataGuard<'_> {
#[must_use]
pub fn as_slice(&self) -> &[u8; PAGE_SIZE] {
self.local.as_ref().map_or(&self.page.backing, |data| data)
}
}
impl std::ops::Deref for PageDataGuard<'_> {
type Target = [u8; PAGE_SIZE];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, thread};
use crate::emulation::memory::page::{Page, PAGE_SIZE};
#[test]
fn test_page_new() {
let mut data = [0u8; PAGE_SIZE];
data[0] = 42;
data[PAGE_SIZE - 1] = 99;
let page = Page::new(data);
assert_eq!(page.read_byte(0).unwrap(), 42);
assert_eq!(page.read_byte(PAGE_SIZE - 1).unwrap(), 99);
assert!(!page.is_modified().unwrap());
}
#[test]
fn test_page_write_cow() {
let page = Page::new([0u8; PAGE_SIZE]);
assert!(!page.is_modified().unwrap());
page.write_byte(100, 0xFF).unwrap();
assert!(page.is_modified().unwrap());
assert_eq!(page.read_byte(100).unwrap(), 0xFF);
assert_eq!(page.read_byte(0).unwrap(), 0); }
#[test]
fn test_page_fork() {
let page = Page::new([42u8; PAGE_SIZE]);
page.write_byte(0, 100).unwrap();
let forked = page.fork().unwrap();
assert_eq!(forked.read_byte(0).unwrap(), 100);
assert!(!forked.is_modified().unwrap());
forked.write_byte(0, 200).unwrap();
assert_eq!(page.read_byte(0).unwrap(), 100);
assert_eq!(forked.read_byte(0).unwrap(), 200);
}
#[test]
fn test_page_read_write_range() {
let page = Page::zeroed();
let data = [1, 2, 3, 4, 5];
page.write(10, &data).unwrap();
let mut buf = [0u8; 5];
page.read(10, &mut buf).unwrap();
assert_eq!(buf, [1, 2, 3, 4, 5]);
let vec = page.read_vec(10, 5).unwrap();
assert_eq!(vec, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_page_from_slice() {
let data = vec![1, 2, 3, 4, 5];
let page = Page::from_slice(&data);
assert_eq!(page.read_byte(0).unwrap(), 1);
assert_eq!(page.read_byte(4).unwrap(), 5);
assert_eq!(page.read_byte(5).unwrap(), 0); }
#[test]
fn test_page_data_guard() {
let mut data = [0u8; PAGE_SIZE];
data[0] = 42;
let page = Page::new(data);
let guard = page.data().unwrap();
assert_eq!(guard[0], 42);
}
#[test]
fn test_page_concurrent_reads() {
let page = Arc::new(Page::new([42u8; PAGE_SIZE]));
let handles: Vec<_> = (0..4)
.map(|_| {
let p = Arc::clone(&page);
thread::spawn(move || {
for _ in 0..1000 {
assert_eq!(p.read_byte(0).unwrap(), 42);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}
#[test]
fn test_page_out_of_bounds_read_byte() {
let page = Page::zeroed();
let result = page.read_byte(PAGE_SIZE);
assert!(result.is_err());
}
#[test]
fn test_page_out_of_bounds_write_byte() {
let page = Page::zeroed();
let result = page.write_byte(PAGE_SIZE, 0xFF);
assert!(result.is_err());
}
#[test]
fn test_page_out_of_bounds_read_range() {
let page = Page::zeroed();
let mut buf = [0u8; 10];
let result = page.read(PAGE_SIZE - 5, &mut buf);
assert!(result.is_err());
}
#[test]
fn test_page_out_of_bounds_write_range() {
let page = Page::zeroed();
let data = [0u8; 10];
let result = page.write(PAGE_SIZE - 5, &data);
assert!(result.is_err());
}
}