use super::*;
use crate::active::{ActiveReadOwner, ActiveWriteOwner};
use crate::core::layout::{
AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSpec, RoleId, ValidationExpectations,
};
use std::alloc::{Layout, alloc_zeroed, dealloc};
const SCHEMA_ID: [u8; 32] = [7; 32];
const GENERATION: u64 = 9;
const SLOT_INDEX: u32 = 0;
const SEQUENCE: u64 = 1;
const PAYLOAD: &[u8] = b"ping-payload";
struct Backing {
base: NonNull<u8>,
len: usize,
}
impl Backing {
fn new(len: usize) -> Self {
let layout = Layout::from_size_align(len, 64).unwrap();
let base = NonNull::new(unsafe { alloc_zeroed(layout) }).unwrap();
Self { base, len }
}
fn bytes(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.base.as_ptr(), self.len) }
}
fn bytes_mut(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.base.as_ptr(), self.len) }
}
}
impl Drop for Backing {
fn drop(&mut self) {
unsafe {
dealloc(
self.base.as_ptr(),
Layout::from_size_align(self.len, 64).unwrap(),
);
}
}
}
struct BackingReadOwner {
base: NonNull<u8>,
len: usize,
}
unsafe impl Send for BackingReadOwner {}
unsafe impl Sync for BackingReadOwner {}
unsafe impl ActiveReadOwner for BackingReadOwner {
fn as_ptr(&self) -> *const u8 {
self.base.as_ptr()
}
fn len(&self) -> usize {
self.len
}
fn page_size(&self) -> usize {
64
}
}
struct BackingWriteOwner {
base: NonNull<u8>,
len: usize,
}
unsafe impl Send for BackingWriteOwner {}
unsafe impl ActiveWriteOwner for BackingWriteOwner {
fn as_ptr(&self) -> *const u8 {
self.base.as_ptr()
}
fn as_mut_ptr(&mut self) -> *mut u8 {
self.base.as_ptr()
}
fn len(&self) -> usize {
self.len
}
fn page_size(&self) -> usize {
64
}
}
fn build_topology() -> RegionSetLayout {
let producer = RoleId::new(1).unwrap();
let acknowledger = RoleId::new(2).unwrap();
let specs = [
RegionSpec {
role: producer,
writer: Endpoint::Initiator,
slot_count: 1,
payload_bytes: 64,
acknowledgement_count: 1,
},
RegionSpec {
role: acknowledger,
writer: Endpoint::Responder,
slot_count: 1,
payload_bytes: 16,
acknowledgement_count: 1,
},
];
let route_specs = [
AcknowledgementRouteSpec {
owner: acknowledger,
target: producer,
slot_index: 0,
cell_index: 0,
},
AcknowledgementRouteSpec {
owner: producer,
target: acknowledger,
slot_index: 0,
cell_index: 0,
},
];
let limits = LayoutLimits {
maximum_mapping_size: 4096,
maximum_slot_count: 2,
maximum_acknowledgement_count: 2,
maximum_payload_bytes: 64,
};
RegionSetLayout::calculate(SCHEMA_ID, GENERATION, &specs, &route_specs, limits).unwrap()
}
fn producer_role() -> RoleId {
RoleId::new(1).unwrap()
}
fn producer_total(topology: &RegionSetLayout) -> usize {
topology.region(producer_role()).unwrap().total_size() as usize
}
fn validate_producer(backing: &mut Backing, topology: &RegionSetLayout) -> ValidatedRegionLayout {
topology
.region(producer_role())
.unwrap()
.encode_into(backing.bytes_mut())
.unwrap();
unsafe {
ValidatedRegionLayout::validate(
backing.bytes(),
ValidationExpectations {
schema_id: SCHEMA_ID,
generation: GENERATION,
role: producer_role(),
writer: Endpoint::Initiator,
maximum_mapping_size: 4096,
},
topology,
)
}
.unwrap()
}
fn read_owner(backing: &Backing, len: usize) -> Box<BackingReadOwner> {
Box::new(BackingReadOwner {
base: backing.base,
len,
})
}
fn write_owner(backing: &Backing, len: usize) -> Box<BackingWriteOwner> {
Box::new(BackingWriteOwner {
base: backing.base,
len,
})
}
fn publish_via_write_bind(
backing: &Backing,
total: usize,
layout: &ValidatedRegionLayout,
topology: &RegionSetLayout,
) {
let writer = ActiveWriter::new_unleased_for_test(write_owner(backing, total), total).unwrap();
let mut region = writer.bind(layout.clone(), topology.clone()).unwrap();
region.publish(SLOT_INDEX, SEQUENCE, None, PAYLOAD).unwrap();
}
#[test]
fn witnesses_carry_expected_thread_markers() {
fn assert_send<T: Send>() {}
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<BoundReadMapping>();
assert_send::<BoundWriteMapping>();
}
#[test]
fn bound_read_round_trip_copies_published_payload() {
let topology = build_topology();
let total = producer_total(&topology);
let mut backing = Backing::new(total);
let layout = validate_producer(&mut backing, &topology);
publish_via_write_bind(&backing, total, &layout, &topology);
let reader = ActiveReader::new_unleased_for_test(read_owner(&backing, total), total).unwrap();
let bound = reader.bind(layout, topology).unwrap();
let mut destination = [0_u8; 64];
let copied = bound
.copy_payload_into(SLOT_INDEX, SEQUENCE, &mut destination)
.unwrap();
assert_eq!(copied, PAYLOAD.len());
assert_eq!(&destination[..copied], PAYLOAD);
}
#[test]
fn bound_write_publishes_payload_observed_by_bound_read() {
let topology = build_topology();
let total = producer_total(&topology);
let mut backing = Backing::new(total);
let layout = validate_producer(&mut backing, &topology);
let writer = ActiveWriter::new_unleased_for_test(write_owner(&backing, total), total).unwrap();
let mut region = writer.bind(layout.clone(), topology.clone()).unwrap();
region.publish(SLOT_INDEX, SEQUENCE, None, PAYLOAD).unwrap();
let recovered = region.into_mapping().into_active();
assert_eq!(recovered.len(), total);
drop(recovered);
let reader = ActiveReader::new_unleased_for_test(read_owner(&backing, total), total).unwrap();
let bound = reader.bind(layout, topology).unwrap();
let observed = bound.copy_payload(SLOT_INDEX, SEQUENCE).unwrap();
assert_eq!(observed, PAYLOAD);
}
#[test]
fn rejected_read_bind_returns_recoverable_reader() {
let topology = build_topology();
let total = producer_total(&topology);
let mut backing = Backing::new(total);
let layout = validate_producer(&mut backing, &topology);
let short_len = total - 64;
let reader =
ActiveReader::new_unleased_for_test(read_owner(&backing, total), short_len).unwrap();
let rejected = match reader.bind(layout, topology) {
Ok(_) => panic!("expected the length-mismatched bind to be rejected"),
Err(rejected) => rejected,
};
assert!(matches!(
rejected.error,
BindingError::MappingSizeMismatch { expected, actual }
if expected == total && actual == short_len
));
let recovered = rejected.into_inner();
assert_eq!(recovered.len(), short_len);
let mut destination = [0_u8; 4];
recovered.read_into(0, &mut destination).unwrap();
}
#[test]
fn bound_read_into_mapping_into_active_returns_usable_reader() {
let topology = build_topology();
let total = producer_total(&topology);
let mut backing = Backing::new(total);
let layout = validate_producer(&mut backing, &topology);
publish_via_write_bind(&backing, total, &layout, &topology);
let reader = ActiveReader::new_unleased_for_test(read_owner(&backing, total), total).unwrap();
let bound = reader.bind(layout.clone(), topology.clone()).unwrap();
let recovered = bound.into_mapping().into_active();
assert_eq!(recovered.len(), total);
let rebound = recovered.bind(layout, topology).unwrap();
let mut destination = [0_u8; 64];
let copied = rebound
.copy_payload_into(SLOT_INDEX, SEQUENCE, &mut destination)
.unwrap();
assert_eq!(&destination[..copied], PAYLOAD);
}
#[test]
fn bound_write_into_mapping_into_active_returns_usable_writer() {
let topology = build_topology();
let total = producer_total(&topology);
let mut backing = Backing::new(total);
let layout = validate_producer(&mut backing, &topology);
let writer = ActiveWriter::new_unleased_for_test(write_owner(&backing, total), total).unwrap();
let region = writer.bind(layout.clone(), topology.clone()).unwrap();
let recovered = region.into_mapping().into_active();
assert_eq!(recovered.len(), total);
let mut rebound = recovered.bind(layout, topology).unwrap();
rebound
.publish(SLOT_INDEX, SEQUENCE, None, PAYLOAD)
.unwrap();
}