use std::{borrow::Cow, io::Write, num::NonZeroU16, path::Path};
use base64::{engine::general_purpose::STANDARD_NO_PAD, write::EncoderWriter as Base64Encoder};
use memmap2::MmapMut;
use crate::Encoder;
#[derive(Debug, PartialEq)]
pub struct ChunkSize(NonZeroU16);
impl ChunkSize {
pub fn new(size: NonZeroU16) -> Option<Self> {
(size.get() <= 1024).then_some(Self(size))
}
}
impl Default for ChunkSize {
fn default() -> Self {
Self(const { NonZeroU16::new(1024).unwrap() })
}
}
#[derive(Debug, PartialEq)]
pub enum Medium<'data> {
Direct {
chunk_size: Option<ChunkSize>,
data: Cow<'data, [u8]>
},
File(Box<Path>),
TempFile(Box<Path>),
SharedMemObject(SharedMemObject)
}
#[cfg(unix)]
#[derive(Debug)]
struct UnixShm {
shm: psx_shm::UnlinkOnDrop,
map: MmapMut
}
#[derive(Debug)]
pub struct SharedMemObject {
#[cfg(unix)]
inner: UnixShm,
#[cfg(windows)]
inner: winmmf::MemoryMappedFile<mmf::RwLock<'static>>
}
impl PartialEq for SharedMemObject {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl SharedMemObject {
#[cfg(unix)]
pub fn create_new(name: &str, size: usize) -> std::io::Result<Self> {
use psx_shm::UnlinkOnDrop;
use rustix::{fs::Mode, shm::OFlags};
let mut shm = psx_shm::Shm::open(
name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR
)?;
shm.set_size(size)?;
let borrowed_mmap = unsafe { shm.map(0) }?;
let map = unsafe { borrowed_mmap.into_map() };
Ok(Self {
inner: UnixShm {
shm: UnlinkOnDrop { shm },
map
}
})
}
#[cfg(windows)]
pub unsafe fn new(inner: winmmf::MemoryMappedFile<mmf::RwLock<'static>>) -> Self {
Self { inner }
}
fn name(&self) -> Box<str> {
#[cfg(unix)]
{
self.inner.shm.shm.name().into()
}
#[cfg(windows)]
{
self.inner.fullname().into()
}
}
pub fn copy_in_buf(&mut self, buf: &[u8]) -> std::io::Result<()> {
let buf_len = buf.len();
#[cfg(unix)]
{
use std::cmp::Ordering;
match buf_len.cmp(&self.inner.map.as_ref().len()) {
Ordering::Less => {
self.inner.map[..buf_len].copy_from_slice(buf);
for byte in &mut self.inner.map[buf_len..] {
*byte = 0;
}
}
Ordering::Equal => self.inner.map.copy_from_slice(buf),
Ordering::Greater =>
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Provided buffer is too large to fit"
)),
}
Ok(())
}
#[cfg(windows)]
{
use std::io::{Error as IOError, ErrorKind};
use winmmf::err::Error;
match self.inner.write(buf) {
Ok(()) => Ok(()),
Err(e) => match e {
Error::ReadLocked => IOError::new(
ErrorKind::ResourceBusy,
"The MMF is locked by a reader, so we can't write to it at the moment"
),
Error::WriteLocked => IOError::new(
ErrorKind::ResourceBusy,
"The MMF is locked by another writer, so we can't write to it at the moment"
),
Error::MaxReaders => IOError::new(
ErrorKind::QuotaExceeded,
"More than 128 readers were active at the same time; back off"
),
Error::NotEnoughMemory => IOError::new(
ErrorKind::OutOfMemory,
"The MMF does not have enough memory to store this buffer"
),
Error::MMF_NotFound => IOError::other(
"The MMF was opened as read-only, was already closed, or couldn't be initialized properly"
),
Error::LockViolation =>
IOError::other("A lock violation occurred with this MMF"),
Error::MaxTriesReached => IOError::new(
ErrorKind::ResourceBusy,
"The MMF is locked and we spun for the max amount of times to try to get access without success"
),
Error::GeneralFailure => IOError::other("A general error occurred"),
Error::OS_Err(e) | Error::OS_OK(e) => IOError::other(e)
}
}
}
}
}
pub(crate) fn write_b64<W: Write>(data: &[u8], writer: W) -> std::io::Result<W> {
let mut b64 = Base64Encoder::new(writer, &STANDARD_NO_PAD);
b64.write_all_allow_empty(data)?;
b64.finish()
}
impl Medium<'_> {
pub(crate) fn write_data<W: Write>(&self, mut writer: W) -> Result<W, std::io::Error> {
let name: Box<str>;
let (key, data) = match self {
Self::Direct { data, chunk_size } => {
if let Some(chunk_size) = chunk_size {
write!(writer, ",t=d,")?;
let b64_encoded_bytes_per_chunk = usize::from(chunk_size.0.get()) * 4;
let pre_encoded_bytes_per_chunk = (b64_encoded_bytes_per_chunk / 4) * 3;
let total_chunks = data.len().div_ceil(pre_encoded_bytes_per_chunk);
let mut chunks = data.chunks(pre_encoded_bytes_per_chunk);
if let Some(first) = chunks.next() {
write!(writer, "m={};", u8::from(total_chunks != 1))?;
writer = write_b64(first, writer)?;
write!(writer, "\x1b\\")?;
}
for (idx, chunk) in chunks.enumerate() {
write!(writer, "\x1b_Gm={};", u8::from(total_chunks != idx + 2))?;
writer = write_b64(chunk, writer)?;
write!(writer, "\x1b\\")?;
}
return Ok(writer);
}
('d', &**data)
}
Self::File(path) => ('f', path.as_os_str().as_encoded_bytes()),
Self::TempFile(path) => ('t', path.as_os_str().as_encoded_bytes()),
Self::SharedMemObject(obj) => {
name = obj.name();
('s', name.as_bytes())
}
};
write!(writer, ",t={key};")?;
let mut writer = Base64Encoder::new(writer, &STANDARD_NO_PAD);
writer.write_all_allow_empty(data)?;
writer.finish()
}
}