use std::{borrow::Cow, io::Write, num::NonZeroU16, path::Path};
use base64_simd::{Base64, Out};
use crate::Encoder as _;
#[derive(Debug, PartialEq)]
pub struct ChunkSize(NonZeroU16);
impl ChunkSize {
#[must_use]
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: memmap2::MmapMut
}
#[derive(Debug)]
pub struct SharedMemObject {
#[cfg(unix)]
inner: UnixShm,
#[cfg(windows)]
inner: winmmf::MemoryMappedFile<winmmf::RWLock<'static>>
}
impl PartialEq for SharedMemObject {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
#[cfg(unix)]
#[derive(Debug)]
pub struct ShmError {
pub step: ShmCreationFailureStep,
pub err: std::io::Error
}
#[cfg(unix)]
impl std::fmt::Display for ShmError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { step, err } = self;
match step {
ShmCreationFailureStep::ShmOpen => write!(f, "Couldn't open SHM: {err}"),
ShmCreationFailureStep::ShmSetSize =>
write!(f, "Couldn't set the size of the SHM: {err}"),
ShmCreationFailureStep::MemmapCreation => write!(
f,
"Couldn't create the memmap necessary to write to this SHM: {err}"
)
}
}
}
#[cfg(unix)]
impl core::error::Error for ShmError {}
#[cfg(unix)]
#[derive(Debug)]
pub enum ShmCreationFailureStep {
ShmOpen,
ShmSetSize,
MemmapCreation
}
impl SharedMemObject {
#[cfg(unix)]
pub fn create_new(name: &str, size: usize) -> Result<Self, ShmError> {
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
)
.map_err(|err| ShmError {
step: ShmCreationFailureStep::ShmOpen,
err
})?;
shm.set_size(size).map_err(|err| ShmError {
step: ShmCreationFailureStep::ShmSetSize,
err
})?;
let borrowed_mmap = unsafe { shm.map(0) }.map_err(|err| ShmError {
step: ShmCreationFailureStep::MemmapCreation,
err
})?;
let map = unsafe { borrowed_mmap.into_map() };
Ok(Self {
inner: UnixShm {
shm: UnlinkOnDrop { shm },
map
}
})
}
#[cfg(windows)]
pub fn create_new(
name: String,
size: core::num::NonZeroUsize
) -> Result<Self, winmmf::err::Error> {
use winmmf::{MemoryMappedFile, RWLock, mmf::Namespace};
MemoryMappedFile::<RWLock<'static>>::new(size, name, Namespace::LOCAL, None)
.map(|inner| 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::{Mmf as _, err::Error};
self.inner.write(buf)
.map_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::Uninitialized => IOError::new(
ErrorKind::InvalidData,
"The MMF is uninitialized, cannot write to it. Please recreate it and try again."
),
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::LargePagePermissionError => IOError::new(
ErrorKind::PermissionDenied,
"This process could not acquire the necesssary permissions to use large pages, but this MMF requires large pages."
),
Error::GeneralFailure => IOError::other("An inspecific, general error occurred"),
Error::OS_Err(e) | Error::OS_OK(e) => IOError::other(e)
})
}
}
}
pub(crate) fn write_b64<W: Write>(data: &[u8], mut writer: W) -> std::io::Result<W> {
const BUF_SIZE: usize = 1024;
let mut buf = [0u8; BUF_SIZE];
const ENCODER: Base64 = base64_simd::STANDARD_NO_PAD;
for chunk in data.chunks(const { ENCODER.estimated_decoded_length(BUF_SIZE) }) {
let encoded_chunk = ENCODER.encode(chunk, Out::from_slice(buf.as_mut_slice()));
writer.write_all_allow_empty(encoded_chunk)?;
}
Ok(writer)
}
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};")?;
write_b64(data, writer)
}
}