use std::cell::UnsafeCell;
use std::mem;
use std::sync::atomic::{AtomicU32, AtomicU8, Ordering};
use crate::errors::{Error, Result};
use crate::system_properties::PROP_VALUE_MAX;
#[cfg(feature = "builder")]
const LONG_LEGACY_ERROR: &str = "Must use __system_property_read_callback() to read";
const LONG_FLAG: u32 = 1 << 16;
const LONG_LEGACY_ERROR_BUFFER_SIZE: usize = 56;
#[cfg(feature = "builder")]
const _: () = assert!(LONG_LEGACY_ERROR.len() < LONG_LEGACY_ERROR_BUFFER_SIZE);
const _: () = assert!(mem::size_of::<AtomicU8>() == mem::size_of::<u8>());
const _: () = assert!(mem::align_of::<AtomicU8>() == mem::align_of::<u8>());
const _: () = assert!(mem::size_of::<AtomicU32>() == mem::size_of::<u32>());
const _: () = assert!(mem::align_of::<AtomicU32>() == mem::align_of::<u32>());
const _: () = assert!(mem::size_of::<[AtomicU8; PROP_VALUE_MAX]>() == PROP_VALUE_MAX);
const _: () = assert!(mem::size_of::<PropertyInfo>() == 4 + PROP_VALUE_MAX);
const _: () = assert!(mem::offset_of!(PropertyInfo, serial) == 0);
const _: () = assert!(mem::offset_of!(PropertyInfo, data) == 4);
#[repr(C)]
struct LongProperty {
error_message: [u8; LONG_LEGACY_ERROR_BUFFER_SIZE],
offset: AtomicU32,
}
#[repr(C)]
union Union {
value: mem::ManuallyDrop<[AtomicU8; PROP_VALUE_MAX]>,
long_property: mem::ManuallyDrop<LongProperty>,
}
#[repr(C, align(4))]
pub(crate) struct PropertyInfo {
pub(crate) serial: AtomicU32,
data: UnsafeCell<Union>,
}
unsafe impl Sync for PropertyInfo {}
impl PropertyInfo {
#[cfg(feature = "builder")]
pub(crate) fn init_with_long_offset(&mut self, offset: u32) {
let error_bytes = LONG_LEGACY_ERROR.as_bytes();
let serial_value = ((error_bytes.len() as u32) << 24) | LONG_FLAG;
self.serial.store(serial_value, Ordering::Relaxed);
unsafe {
let long_property = &mut *(*self.data.get()).long_property;
long_property.error_message =
error_bytes_padded::<LONG_LEGACY_ERROR_BUFFER_SIZE>(error_bytes);
*long_property.offset.get_mut() = offset;
}
}
#[cfg(feature = "builder")]
pub(crate) fn init_with_value(&mut self, value: &str) {
debug_assert!(
value.len() < PROP_VALUE_MAX,
"init_with_value: value of {} bytes must use the long variant",
value.len()
);
let stored_len = value.len().min(PROP_VALUE_MAX - 1);
let serial_value = (stored_len as u32) << 24;
self.serial.store(serial_value, Ordering::Relaxed);
unsafe {
let slot = &mut *(*self.data.get()).value;
init_value_bytes(slot, value.as_bytes());
}
}
pub(crate) fn short_value_bytes<'a>(&self, buf: &'a mut [u8; PROP_VALUE_MAX]) -> &'a [u8] {
debug_assert!(!self.is_long(), "short_value_bytes on a LONG entry");
unsafe {
let slot = &*(*self.data.get()).value;
read_value_atomic(slot, buf)
}
}
pub(crate) fn long_offset(&self) -> Result<u32> {
if !self.is_long() {
return Err(Error::InvalidArgument(
"long_offset called on a short property entry".into(),
));
}
unsafe {
let long_property = &*(*self.data.get()).long_property;
Ok(long_property.offset.load(Ordering::Relaxed))
}
}
pub(crate) fn is_long(&self) -> bool {
let serial = self.serial.load(Ordering::Relaxed);
serial & LONG_FLAG != 0
}
#[cfg(feature = "builder")]
pub(crate) fn writer(&mut self) -> PropertyInfoWriter<'_> {
PropertyInfoWriter(self)
}
}
#[cfg(feature = "builder")]
pub(crate) struct PropertyInfoWriter<'a>(&'a mut PropertyInfo);
#[cfg(feature = "builder")]
const SERIAL_COUNTER_MASK: u32 = 0x00ff_ffff & !LONG_FLAG;
#[cfg(feature = "builder")]
impl PropertyInfoWriter<'_> {
pub(crate) fn apply_write(self, value: &str) -> Result<u32> {
let current = self.0.serial.load(Ordering::Relaxed);
if current & LONG_FLAG != 0 {
return Err(Error::InvalidArgument(format!(
"in-place update of long property is not supported (serial={current:#x})"
)));
}
if value.len() >= PROP_VALUE_MAX {
return Err(Error::InvalidArgument(format!(
"Value too long: {} (max: {})",
value.len(),
PROP_VALUE_MAX - 1
)));
}
let len_u32 = value.len() as u32;
let dirty_serial = current | 1;
let counter_next = (dirty_serial.wrapping_add(1)) & SERIAL_COUNTER_MASK;
let new_serial = (len_u32 << 24) | counter_next;
self.0.serial.store(dirty_serial, Ordering::Release);
std::sync::atomic::fence(Ordering::Release);
unsafe {
let slot = &*(*self.0.data.get()).value;
write_value_atomic(slot, value.as_bytes());
}
self.0.serial.store(new_serial, Ordering::Release);
Ok(new_serial)
}
}
#[cfg(feature = "builder")]
fn error_bytes_padded<const N: usize>(src: &[u8]) -> [u8; N] {
let mut buf = [0u8; N];
let copy_len = src.len().min(N);
buf[..copy_len].copy_from_slice(&src[..copy_len]);
buf
}
fn read_value_atomic<'a>(
slot: &[AtomicU8; PROP_VALUE_MAX],
buf: &'a mut [u8; PROP_VALUE_MAX],
) -> &'a [u8] {
let mut len = 0;
for (i, cell) in slot.iter().enumerate() {
let b = cell.load(Ordering::Relaxed);
if b == 0 {
break;
}
buf[i] = b;
len = i + 1;
}
&buf[..len]
}
#[cfg(feature = "builder")]
fn write_value_atomic(slot: &[AtomicU8; PROP_VALUE_MAX], bytes: &[u8]) {
let copy_len = bytes.len().min(PROP_VALUE_MAX - 1);
for (i, &b) in bytes[..copy_len].iter().enumerate() {
slot[i].store(b, Ordering::Relaxed);
}
slot[copy_len].store(0, Ordering::Relaxed);
}
#[cfg(feature = "builder")]
fn init_value_bytes(slot: &mut [AtomicU8; PROP_VALUE_MAX], bytes: &[u8]) {
let copy_len = bytes.len().min(PROP_VALUE_MAX - 1);
for (i, &b) in bytes[..copy_len].iter().enumerate() {
*slot[i].get_mut() = b;
}
*slot[copy_len].get_mut() = 0;
}