use super::*;
pub unsafe fn transmute_record_bytes<T: HasRType>(bytes: &[u8]) -> Option<&T> {
assert!(
bytes.len() >= mem::size_of::<T>(),
"Passing a slice smaller than `{}` to `transmute_record_bytes` is invalid",
std::any::type_name::<T>()
);
let non_null = NonNull::new_unchecked(bytes.as_ptr().cast_mut());
if T::has_rtype(non_null.cast::<RecordHeader>().as_ref().rtype) {
Some(non_null.cast::<T>().as_ref())
} else {
None
}
}
pub unsafe fn transmute_header_bytes(bytes: &[u8]) -> Option<&RecordHeader> {
assert!(
bytes.len() >= mem::size_of::<RecordHeader>(),
concat!(
"Passing a slice smaller than `",
stringify!(RecordHeader),
"` to `transmute_header_bytes` is invalid"
)
);
let non_null = NonNull::new_unchecked(bytes.as_ptr().cast_mut());
let header = non_null.cast::<RecordHeader>().as_ref();
if header.record_size() > bytes.len() {
None
} else {
Some(header)
}
}
pub unsafe fn transmute_record<T: HasRType>(header: &RecordHeader) -> Option<&T> {
if T::has_rtype(header.rtype) {
let non_null = NonNull::from(header);
Some(non_null.cast::<T>().as_ref())
} else {
None
}
}
pub(crate) unsafe fn as_u8_slice<T: Sized>(data: &T) -> &[u8] {
slice::from_raw_parts((data as *const T).cast(), mem::size_of::<T>())
}
pub unsafe fn transmute_record_mut<T: HasRType>(header: &mut RecordHeader) -> Option<&mut T> {
if T::has_rtype(header.rtype) {
let non_null = NonNull::from(header);
Some(non_null.cast::<T>().as_mut())
} else {
None
}
}
pub fn str_to_c_chars<const N: usize>(s: &str) -> Result<[c_char; N]> {
let s = s.as_bytes();
if s.len() > (N - 1) {
return Err(Error::encode(format!(
"string cannot be longer than {}; received str of length {}",
N - 1,
s.len(),
)));
}
let mut res = [0; N];
res[..s.len()].copy_from_slice(
unsafe { std::mem::transmute::<&[u8], &[c_char]>(s) },
);
Ok(res)
}
pub fn c_chars_to_str<const N: usize>(chars: &[c_char; N]) -> Result<&str> {
let bytes = unsafe { as_u8_slice(chars) };
let cstr = CStr::from_bytes_until_nul(bytes).map_err(|_| Error::Conversion {
input: format!("{chars:?}"),
desired_type: "CStr (null-terminated)",
})?;
cstr.to_str()
.map_err(|e| Error::utf8(e, format!("converting c_char array: {chars:?}")))
}
pub fn ts_to_dt(ts: u64) -> Option<time::OffsetDateTime> {
if ts == crate::UNDEF_TIMESTAMP {
None
} else {
Some(time::OffsetDateTime::from_unix_timestamp_nanos(ts as i128).unwrap())
}
}
#[cfg(feature = "serde")]
pub(crate) mod cstr_serde {
use std::ffi::c_char;
use serde::{de, ser, Deserialize, Deserializer, Serializer};
use super::{c_chars_to_str, str_to_c_chars};
pub fn serialize<S, const N: usize>(
chars: &[c_char; N],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(c_chars_to_str(chars).map_err(ser::Error::custom)?)
}
pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[c_char; N], D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
str_to_c_chars(&str).map_err(de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::raw::c_char;
#[test]
fn test_c_chars_to_str_success() {
let null_terminated: [c_char; 5] = [
'A' as c_char,
'A' as c_char,
'A' as c_char,
'A' as c_char,
0,
];
let result = c_chars_to_str(&null_terminated);
assert_eq!(result.unwrap(), "AAAA");
}
#[test]
fn test_c_chars_to_str_failure_on_missing_null_terminator() {
let non_null_terminated: [c_char; 5] = ['A' as c_char; 5];
let err = c_chars_to_str(&non_null_terminated)
.expect_err("Expected failure on non-null terminated string");
assert!(matches!(
err,
Error::Conversion {
input: _,
desired_type: "CStr (null-terminated)",
}
));
}
}