use super::PhysicalLayout;
use std::collections::HashSet;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum BlockValidationError {
#[error("Destination block IDs are not unique: duplicates = {duplicates:?}")]
DuplicateDestinationBlocks { duplicates: Vec<usize> },
#[error("Source and destination blocks overlap (same layout): overlapping = {overlapping:?}")]
OverlappingBlocks { overlapping: Vec<usize> },
#[error(
"Block ID lists have mismatched lengths: src={src_len}, dst={dst_len}, bounce={bounce_len:?}"
)]
LengthMismatch {
src_len: usize,
dst_len: usize,
bounce_len: Option<usize>,
},
#[error("Block ID {block_id} out of range for {layout_name} (max={max})")]
BlockOutOfRange {
block_id: usize,
layout_name: &'static str,
max: usize,
},
#[error("Bounce block IDs are not unique: duplicates = {duplicates:?}")]
DuplicateBounceBlocks { duplicates: Vec<usize> },
}
pub fn validate_dst_unique(dst_block_ids: &[usize]) -> Result<(), BlockValidationError> {
let mut seen = HashSet::new();
let mut duplicates = Vec::new();
for &id in dst_block_ids {
if !seen.insert(id) && !duplicates.contains(&id) {
duplicates.push(id);
}
}
if duplicates.is_empty() {
Ok(())
} else {
Err(BlockValidationError::DuplicateDestinationBlocks { duplicates })
}
}
pub fn validate_bounce_unique(bounce_block_ids: &[usize]) -> Result<(), BlockValidationError> {
let mut seen = HashSet::new();
let mut duplicates = Vec::new();
for &id in bounce_block_ids {
if !seen.insert(id) && !duplicates.contains(&id) {
duplicates.push(id);
}
}
if duplicates.is_empty() {
Ok(())
} else {
Err(BlockValidationError::DuplicateBounceBlocks { duplicates })
}
}
fn are_same_layout(layout1: &PhysicalLayout, layout2: &PhysicalLayout) -> bool {
std::ptr::eq(
std::sync::Arc::as_ptr(layout1.layout()),
std::sync::Arc::as_ptr(layout2.layout()),
)
}
#[cfg(debug_assertions)]
pub fn validate_disjoint_same_layout(
src_block_ids: &[usize],
dst_block_ids: &[usize],
src_layout: &PhysicalLayout,
dst_layout: &PhysicalLayout,
) -> Result<(), BlockValidationError> {
if !are_same_layout(src_layout, dst_layout) {
return Ok(());
}
let src_set: HashSet<_> = src_block_ids.iter().copied().collect();
let overlapping: Vec<_> = dst_block_ids
.iter()
.filter(|id| src_set.contains(id))
.copied()
.collect();
if overlapping.is_empty() {
Ok(())
} else {
Err(BlockValidationError::OverlappingBlocks { overlapping })
}
}
#[cfg(debug_assertions)]
pub fn validate_block_ids_in_range(
block_ids: &[usize],
layout: &PhysicalLayout,
layout_name: &'static str,
) -> Result<(), BlockValidationError> {
let max_blocks = layout.layout().config().num_blocks;
for &block_id in block_ids {
if block_id >= max_blocks {
return Err(BlockValidationError::BlockOutOfRange {
block_id,
layout_name,
max: max_blocks,
});
}
}
Ok(())
}
#[cfg(debug_assertions)]
pub fn validate_block_transfer(
src_block_ids: &[usize],
dst_block_ids: &[usize],
bounce_block_ids: Option<&[usize]>,
src_layout: &PhysicalLayout,
dst_layout: &PhysicalLayout,
bounce_layout: Option<&PhysicalLayout>,
) -> Result<(), BlockValidationError> {
if src_block_ids.len() != dst_block_ids.len() {
return Err(BlockValidationError::LengthMismatch {
src_len: src_block_ids.len(),
dst_len: dst_block_ids.len(),
bounce_len: bounce_block_ids.map(|ids| ids.len()),
});
}
if let Some(bounce_ids) = bounce_block_ids
&& bounce_ids.len() != src_block_ids.len()
{
return Err(BlockValidationError::LengthMismatch {
src_len: src_block_ids.len(),
dst_len: dst_block_ids.len(),
bounce_len: Some(bounce_ids.len()),
});
}
#[cfg(debug_assertions)]
{
validate_dst_unique(dst_block_ids)?;
if let Some(bounce_ids) = bounce_block_ids {
validate_bounce_unique(bounce_ids)?;
}
validate_disjoint_same_layout(src_block_ids, dst_block_ids, src_layout, dst_layout)?;
validate_block_ids_in_range(src_block_ids, src_layout, "source")?;
validate_block_ids_in_range(dst_block_ids, dst_layout, "destination")?;
if let (Some(bounce_ids), Some(bounce_layout)) = (bounce_block_ids, bounce_layout) {
validate_block_ids_in_range(bounce_ids, bounce_layout, "bounce")?;
}
}
Ok(())
}
#[cfg(not(debug_assertions))]
pub fn validate_block_transfer(
src_block_ids: &[usize],
dst_block_ids: &[usize],
bounce_block_ids: Option<&[usize]>,
_src_layout: &PhysicalLayout,
_dst_layout: &PhysicalLayout,
_bounce_layout: Option<&PhysicalLayout>,
) -> Result<(), BlockValidationError> {
if src_block_ids.len() != dst_block_ids.len() {
return Err(BlockValidationError::LengthMismatch {
src_len: src_block_ids.len(),
dst_len: dst_block_ids.len(),
bounce_len: bounce_block_ids.map(|ids| ids.len()),
});
}
if let Some(bounce_ids) = bounce_block_ids {
if bounce_ids.len() != src_block_ids.len() {
return Err(BlockValidationError::LengthMismatch {
src_len: src_block_ids.len(),
dst_len: dst_block_ids.len(),
bounce_len: Some(bounce_ids.len()),
});
}
}
validate_dst_unique(dst_block_ids)?;
Ok(())
}
#[cfg(all(test, feature = "testing-nixl"))]
mod tests {
use super::super::tests::*;
use super::*;
#[test]
fn test_dst_unique_valid() {
let ids = vec![0, 1, 2, 3, 4];
assert!(validate_dst_unique(&ids).is_ok());
}
#[test]
fn test_dst_unique_duplicate() {
let ids = vec![0, 1, 2, 1, 3];
let result = validate_dst_unique(&ids);
assert!(result.is_err());
match result.unwrap_err() {
BlockValidationError::DuplicateDestinationBlocks { duplicates } => {
assert_eq!(duplicates, vec![1]);
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_dst_unique_multiple_duplicates() {
let ids = vec![0, 1, 2, 1, 3, 2];
let result = validate_dst_unique(&ids);
assert!(result.is_err());
match result.unwrap_err() {
BlockValidationError::DuplicateDestinationBlocks { duplicates } => {
assert!(duplicates.contains(&1));
assert!(duplicates.contains(&2));
}
_ => panic!("Wrong error type"),
}
}
#[test]
#[cfg(debug_assertions)]
fn test_disjoint_same_layout_valid() {
let physical = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let src_ids = vec![0, 1, 2];
let dst_ids = vec![5, 6, 7];
assert!(validate_disjoint_same_layout(&src_ids, &dst_ids, &physical, &physical).is_ok());
}
#[test]
#[cfg(debug_assertions)]
fn test_disjoint_same_layout_overlap() {
let physical = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let src_ids = vec![0, 1, 2];
let dst_ids = vec![2, 3, 4];
let result = validate_disjoint_same_layout(&src_ids, &dst_ids, &physical, &physical);
assert!(result.is_err());
match result.unwrap_err() {
BlockValidationError::OverlappingBlocks { overlapping } => {
assert_eq!(overlapping, vec![2]);
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_disjoint_different_layouts_ok() {
let physical1 = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let physical2 = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let src_ids = vec![0, 1, 2];
let dst_ids = vec![0, 1, 2];
#[cfg(debug_assertions)]
assert!(validate_disjoint_same_layout(&src_ids, &dst_ids, &physical1, &physical2).is_ok());
}
#[test]
fn test_length_mismatch() {
let physical1 = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let physical2 = builder(2)
.fully_contiguous()
.allocate_system()
.build()
.unwrap();
let src_ids = vec![0, 1, 2];
let dst_ids = vec![5, 6];
let result =
validate_block_transfer(&src_ids, &dst_ids, None, &physical1, &physical2, None);
assert!(result.is_err());
match result.unwrap_err() {
BlockValidationError::LengthMismatch {
src_len,
dst_len,
bounce_len,
} => {
assert_eq!(src_len, 3);
assert_eq!(dst_len, 2);
assert_eq!(bounce_len, None);
}
_ => panic!("Wrong error type"),
}
}
}