use std::io::{self, Read, Write};
use std::time::Duration;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
use unix as native;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
use windows as native;
pub const MAX_INSTANCE_NAME_BYTES: usize = 64;
pub const MAX_INSTANCE_MESSAGE_BYTES: usize = 16 * 1024;
pub const INSTANCE_IO_TIMEOUT: Duration = Duration::from_secs(2);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InstanceName(String);
impl InstanceName {
pub fn new(name: &str) -> io::Result<Self> {
let bytes = name.as_bytes();
let valid = !bytes.is_empty()
&& bytes.len() <= MAX_INSTANCE_NAME_BYTES
&& bytes[0].is_ascii_alphanumeric()
&& bytes.iter().all(|byte| {
byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'-')
});
if !valid {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"instance name must be 1 to 64 lowercase letters, digits, '.' or '-'",
));
}
Ok(Self(name.to_owned()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug)]
pub enum InstanceRole {
Primary(PrimaryInstance),
Secondary(SecondaryInstance),
}
#[derive(Debug)]
pub struct PrimaryInstance {
inner: native::Primary,
}
impl PrimaryInstance {
pub fn try_receive(&mut self) -> io::Result<Option<Vec<u8>>> {
self.inner.try_receive()
}
}
#[derive(Debug)]
pub struct SecondaryInstance {
inner: native::Secondary,
}
impl SecondaryInstance {
pub fn send(self, message: &[u8]) -> io::Result<()> {
if message.len() > MAX_INSTANCE_MESSAGE_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"instance message exceeds its bound",
));
}
self.inner.send(message)
}
}
pub fn claim(name: &InstanceName) -> io::Result<InstanceRole> {
native::claim(name).map(|role| match role {
native::Role::Primary(inner) => InstanceRole::Primary(PrimaryInstance { inner }),
native::Role::Secondary(inner) => InstanceRole::Secondary(SecondaryInstance { inner }),
})
}
fn write_frame(writer: &mut impl Write, message: &[u8]) -> io::Result<()> {
let length = u32::try_from(message.len())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "instance message too large"))?;
writer.write_all(&length.to_le_bytes())?;
writer.write_all(message)?;
writer.flush()
}
fn read_frame(reader: &mut impl Read) -> io::Result<Vec<u8>> {
let mut header = [0; 4];
reader.read_exact(&mut header)?;
let length = usize::try_from(u32::from_le_bytes(header)).unwrap_or(usize::MAX);
if length > MAX_INSTANCE_MESSAGE_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"instance message exceeds its bound",
));
}
let mut message = Vec::new();
message
.try_reserve_exact(length)
.map_err(|_| io::Error::new(io::ErrorKind::OutOfMemory, "instance message allocation"))?;
message.resize(length, 0);
reader.read_exact(&mut message)?;
Ok(message)
}
#[cfg(test)]
mod tests;