use std::{
ffi::{CStr, c_char},
fmt,
hash::{Hash, Hasher},
};
#[cfg(feature = "config")]
use serde::{Deserialize, Deserializer, de::Error};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::abi::SandboxSafe;
pub const MAX_PATH_LEN: usize = 256;
pub const MAX_ENGINE_NAME_LEN: usize = 256;
pub const MAX_SUBNET_LEN: usize = 64;
pub const MAX_ENV_ENTRY_LEN: usize = 256;
pub const MAX_ENV_ENTRIES: usize = 32;
#[repr(C)]
#[derive(Clone, Copy, FromBytes, Immutable, KnownLayout)]
pub struct FlatCStr<const N: usize> {
bytes: [u8; N],
}
unsafe impl<const N: usize> IntoBytes for FlatCStr<N> {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
unsafe impl<const N: usize> SandboxSafe for FlatCStr<N> {}
impl<const N: usize> FlatCStr<N> {
pub const fn capacity() -> usize {
N
}
pub fn try_new(s: &str) -> Option<Self> {
assert!(!s.as_bytes().contains(&0), "FlatCStr: interior NUL in {s:?}");
if s.len() + 1 > N {
return None;
}
let mut bytes = [0u8; N];
bytes[..s.len()].copy_from_slice(s.as_bytes());
Some(Self { bytes: bytes })
}
pub fn new(s: &str) -> Self {
Self::try_new(s).unwrap_or_else(|| panic!("FlatCStr: {} bytes exceeds {N}", s.len()))
}
pub fn empty() -> Self {
Self { bytes: [0u8; N] }
}
pub fn as_ptr(&self) -> *const c_char {
self.bytes.as_ptr().cast()
}
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.bytes[..self.len()]).unwrap_or("")
}
pub fn len(&self) -> usize {
self.bytes.iter().position(|&b| b == 0).unwrap_or(N)
}
pub fn is_empty(&self) -> bool {
self.bytes[0] == 0
}
}
impl<const N: usize> Default for FlatCStr<N> {
fn default() -> Self {
Self::empty()
}
}
impl<const N: usize> PartialEq for FlatCStr<N> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> Eq for FlatCStr<N> {}
impl<const N: usize> Hash for FlatCStr<N> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<const N: usize> fmt::Debug for FlatCStr<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("FlatCStr").field(&self.as_str()).finish()
}
}
#[cfg(feature = "config")]
impl<'de, const N: usize> Deserialize<'de> for FlatCStr<N> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
if s.as_bytes().contains(&0) {
return Err(D::Error::custom(format!("string holds a NUL byte: {s:?}")));
}
FlatCStr::try_new(&s)
.ok_or_else(|| D::Error::custom(format!("{} bytes exceeds {N}: {s:?}", s.len())))
}
}
pub fn inline_bytes<const N: usize>(field: &[c_char; N]) -> &[u8] {
unsafe { std::slice::from_raw_parts(field.as_ptr().cast(), N) }
}
pub fn inline_bytes_mut<const N: usize>(field: &mut [c_char; N]) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(field.as_mut_ptr().cast(), N) }
}
pub fn inline_cstr<const N: usize>(field: &[c_char; N]) -> &CStr {
CStr::from_bytes_until_nul(inline_bytes(field)).unwrap_or(c"")
}
pub fn pack_cstr<const N: usize>(src: &CStr) -> [c_char; N] {
const { assert!(N > 0, "an inline name field has room for its terminator") };
let mut out = [0 as c_char; N];
let bytes = src.to_bytes();
let n = bytes.len().min(N - 1);
inline_bytes_mut(&mut out)[..n].copy_from_slice(&bytes[..n]);
out
}