use std::collections::HashMap;
use std::io::{self, Read, Seek, SeekFrom};
use std::sync::{Arc, OnceLock, RwLock};
use oxideav_core::{BytesSource, Error, Result};
use crate::uri;
fn table() -> &'static RwLock<HashMap<String, Arc<Vec<u8>>>> {
static T: OnceLock<RwLock<HashMap<String, Arc<Vec<u8>>>>> = OnceLock::new();
T.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn put<I: Into<String>>(id: I, data: Vec<u8>) {
table()
.write()
.expect("mem:// table poisoned")
.insert(id.into(), Arc::new(data));
}
pub fn remove(id: &str) -> bool {
table()
.write()
.expect("mem:// table poisoned")
.remove(id)
.is_some()
}
pub fn clear() {
table().write().expect("mem:// table poisoned").clear();
}
pub fn open_mem(uri_str: &str) -> Result<Box<dyn BytesSource>> {
let (scheme, rest) = uri::split(uri_str);
if scheme != "mem" {
return Err(Error::invalid(format!(
"mem driver invoked on non-mem URI: {uri_str}"
)));
}
let id = rest;
if id.is_empty() {
return Err(Error::invalid("mem:// URI requires a non-empty id"));
}
if id.contains('/') {
return Err(Error::invalid(format!(
"mem:// id must not contain '/': {id}"
)));
}
let guard = table().read().expect("mem:// table poisoned");
let buf = guard
.get(id)
.ok_or_else(|| Error::invalid(format!("mem:// id '{id}' is not registered")))?;
Ok(Box::new(MemReader::new(Arc::clone(buf))))
}
struct MemReader {
buf: Arc<Vec<u8>>,
pos: u64,
}
impl MemReader {
fn new(buf: Arc<Vec<u8>>) -> Self {
Self { buf, pos: 0 }
}
}
impl Read for MemReader {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if out.is_empty() {
return Ok(0);
}
let len = self.buf.len() as u64;
if self.pos >= len {
return Ok(0);
}
let avail = (len - self.pos) as usize;
let n = out.len().min(avail);
let start = self.pos as usize;
out[..n].copy_from_slice(&self.buf[start..start + n]);
self.pos += n as u64;
Ok(n)
}
}
impl Seek for MemReader {
fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
let len = self.buf.len() as u64;
let new_pos = match from {
SeekFrom::Start(n) => n,
SeekFrom::End(d) => add_signed(len, d)?,
SeekFrom::Current(d) => add_signed(self.pos, d)?,
};
self.pos = new_pos;
Ok(self.pos)
}
}
fn add_signed(base: u64, delta: i64) -> io::Result<u64> {
let result = if delta >= 0 {
base.checked_add(delta as u64)
} else {
base.checked_sub(delta.unsigned_abs())
};
result.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"mem:// reader: seek resolves to a negative or overflowing position",
)
})
}
#[cfg(test)]
mod tests {
use std::io::{Read, Seek, SeekFrom};
use super::*;
fn fresh_id() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
format!("test-{}", N.fetch_add(1, Ordering::Relaxed))
}
#[test]
fn put_open_read_roundtrip() {
let id = fresh_id();
put(&id, b"hello, mem://".to_vec());
let mut r = open_mem(&format!("mem://{id}")).unwrap();
let mut buf = Vec::new();
r.read_to_end(&mut buf).unwrap();
assert_eq!(buf, b"hello, mem://");
assert!(remove(&id));
}
#[test]
fn open_supports_seek() {
let id = fresh_id();
put(&id, (0..=255u8).collect());
let mut r = open_mem(&format!("mem://{id}")).unwrap();
r.seek(SeekFrom::Start(100)).unwrap();
let mut byte = [0u8; 1];
r.read_exact(&mut byte).unwrap();
assert_eq!(byte[0], 100);
let end = r.seek(SeekFrom::End(0)).unwrap();
assert_eq!(end, 256);
assert!(remove(&id));
}
#[test]
fn unknown_id_errors() {
let r = open_mem("mem://does-not-exist-xyz");
assert!(r.is_err());
}
#[test]
fn empty_id_rejected() {
let r = open_mem("mem://");
assert!(r.is_err());
}
#[test]
fn slash_in_id_rejected() {
let r = open_mem("mem://foo/bar");
assert!(r.is_err());
}
#[test]
fn wrong_scheme_rejected() {
let r = open_mem("file:///tmp/x");
assert!(r.is_err());
}
#[test]
fn seek_past_end_then_read_returns_zero() {
let id = fresh_id();
put(&id, b"abcdef".to_vec());
let mut r = open_mem(&format!("mem://{id}")).unwrap();
r.seek(SeekFrom::Start(100)).unwrap();
let mut buf = [0u8; 8];
assert_eq!(r.read(&mut buf).unwrap(), 0);
r.seek(SeekFrom::Start(2)).unwrap();
let mut chunk = [0u8; 3];
r.read_exact(&mut chunk).unwrap();
assert_eq!(&chunk, b"cde");
assert!(remove(&id));
}
#[test]
fn seek_before_zero_errors() {
let id = fresh_id();
put(&id, b"xy".to_vec());
let mut r = open_mem(&format!("mem://{id}")).unwrap();
let err = r.seek(SeekFrom::Current(-1));
assert!(err.is_err());
let err = r.seek(SeekFrom::End(-100));
assert!(err.is_err());
assert!(remove(&id));
}
#[test]
fn large_buffer_open_does_not_copy() {
let id = fresh_id();
let big: Vec<u8> = (0..(2 * 1024 * 1024)).map(|i| (i & 0xff) as u8).collect();
put(&id, big.clone());
let mut readers = Vec::with_capacity(16);
for _ in 0..16 {
readers.push(open_mem(&format!("mem://{id}")).unwrap());
}
for r in readers.iter_mut() {
let mut head = [0u8; 8];
r.read_exact(&mut head).unwrap();
assert_eq!(head, [0, 1, 2, 3, 4, 5, 6, 7]);
}
let pos = readers[0].stream_position().unwrap();
assert_eq!(pos, 8);
assert!(remove(&id));
}
#[test]
fn multiple_opens_are_independent() {
let id = fresh_id();
put(&id, b"AAAAAAAA".to_vec());
let mut r1 = open_mem(&format!("mem://{id}")).unwrap();
let mut r2 = open_mem(&format!("mem://{id}")).unwrap();
let mut a = [0u8; 4];
r1.read_exact(&mut a).unwrap();
let mut b = [0u8; 1];
r2.read_exact(&mut b).unwrap();
assert_eq!(b[0], b'A');
let pos1 = r1.stream_position().unwrap();
let pos2 = r2.stream_position().unwrap();
assert_eq!(pos1, 4);
assert_eq!(pos2, 1);
assert!(remove(&id));
}
}