extern crate alloc;
use alloc::vec::Vec;
use crate::kairos::Kairos;
use crate::metis::{DotSet, Rhapsody, SnapshotDecodeError};
use super::super::d;
fn rank(physical: u64, logical: u16) -> Kairos {
Kairos::new(physical, logical, 1, 0u16)
}
fn empty_suffix(bytes: &mut Vec<u8>) {
bytes.push(0x01u8);
bytes.extend_from_slice(&0u32.to_be_bytes());
}
fn valid_run_frame() -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(0x01u8);
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&4u64.to_be_bytes());
bytes.extend_from_slice(&2u32.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&0u64.to_be_bytes());
bytes.extend_from_slice(&rank(100, 0).to_bytes());
bytes.extend_from_slice(&[0, 0, 0, 0, 0, 50]);
empty_suffix(&mut bytes);
bytes
}
#[test]
fn test_the_valid_frame_decodes_and_reencodes() {
let bytes = valid_run_frame();
let decoded = Rhapsody::from_snapshot_bytes(&bytes).expect("the base frame is valid");
assert_eq!(decoded.skeleton_len(), 2);
assert_eq!(decoded.to_snapshot_bytes(), bytes);
}
#[test]
fn test_unknown_version_is_refused() {
let mut bytes = valid_run_frame();
bytes[0] = 0x7F;
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::UnknownVersion(0x7F))
);
}
#[test]
fn test_truncations_are_refused_at_every_boundary() {
let bytes = valid_run_frame();
for cut in 0..bytes.len() {
match Rhapsody::from_snapshot_bytes(&bytes[..cut]) {
Err(SnapshotDecodeError::UnexpectedLength { .. } | SnapshotDecodeError::Visible(_)) => {
}
other => panic!("cut {cut}: expected a length refusal, got {other:?}"),
}
}
}
#[test]
fn test_trailing_bytes_are_refused_by_from_bytes() {
let mut bytes = valid_run_frame();
bytes.push(0xAA);
assert!(matches!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::UnexpectedLength { .. })
));
let (_, tail) = Rhapsody::from_snapshot_prefix(&bytes).expect("prefix still decodes");
assert_eq!(tail, &[0xAA]);
}
#[test]
fn test_zero_base_is_refused() {
let mut bytes = valid_run_frame();
bytes[13..21].copy_from_slice(&0u64.to_be_bytes());
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::ZeroDot { station: 1 })
);
}
#[test]
fn test_a_one_element_run_is_refused() {
let mut bytes = valid_run_frame();
bytes[21..25].copy_from_slice(&1u32.to_be_bytes());
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::RunTooShort {
station: 1,
base: 4
})
);
}
#[test]
fn test_a_run_past_the_dot_ceiling_is_refused() {
let mut bytes = valid_run_frame();
bytes[13..21].copy_from_slice(&u64::MAX.to_be_bytes());
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::RunOverflow {
station: 1,
base: u64::MAX,
len: 2
})
);
}
#[test]
fn test_an_unknown_anchor_tag_is_refused() {
let mut bytes = valid_run_frame();
bytes[25] = 0x03;
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::BadAnchorTag { tag: 0x03 })
);
}
#[test]
fn test_a_padded_origin_anchor_is_refused() {
let mut bytes = valid_run_frame();
bytes[29] = 0x01;
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::NonCanonicalOriginAnchor {
station: 1,
index: 4
})
);
}
#[test]
fn test_a_bad_rank_version_is_refused() {
let mut bytes = valid_run_frame();
bytes[38] = 0x02;
assert!(matches!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::Rank(_))
));
}
#[test]
fn test_a_rank_step_past_the_physical_ceiling_is_refused() {
let mut bytes = valid_run_frame();
bytes[39..47].copy_from_slice(&u64::MAX.to_be_bytes());
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::RankOverflow {
station: 1,
index: 5
})
);
}
#[test]
fn test_the_rollover_overlap_is_refused_as_non_canonical() {
let mut bytes = valid_run_frame();
bytes[39..47].copy_from_slice(&100u64.to_be_bytes());
bytes[47..49].copy_from_slice(&(u16::MAX - 1).to_be_bytes());
bytes[55..61].copy_from_slice(&[0, 0, 0, 0, 0, 1]);
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::NonCanonicalRankStep {
station: 1,
index: 5
})
);
bytes[55..61].copy_from_slice(&[0, 0, 0, 0, 0, 0]);
let decoded = Rhapsody::from_snapshot_bytes(&bytes).expect("the successor spelling decodes");
let interior = decoded.locus(d(1, 5)).expect("interior locus");
assert_eq!(
(interior.rank.physical(), interior.rank.logical()),
(101, 0)
);
}
#[test]
fn test_a_split_chain_is_refused() {
let mut bytes = Vec::new();
bytes.push(0x01u8);
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&2u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&4u64.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&rank(100, 0).to_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&5u64.to_be_bytes());
bytes.push(0x01);
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&4u64.to_be_bytes());
bytes.extend_from_slice(&rank(150, 0).to_bytes());
empty_suffix(&mut bytes);
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::SplitChain {
station: 1,
index: 5
})
);
}
#[test]
fn test_an_unchainable_neighbor_pair_rides_free_and_decodes() {
let mut bytes = Vec::new();
bytes.push(0x01u8);
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&2u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&4u64.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&rank(100, 0).to_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&5u64.to_be_bytes());
bytes.push(0x02);
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&4u64.to_be_bytes());
bytes.extend_from_slice(&rank(150, 0).to_bytes());
empty_suffix(&mut bytes);
let decoded = Rhapsody::from_snapshot_bytes(&bytes).expect("unchainable pair decodes free");
assert_eq!(decoded.skeleton_len(), 2);
assert_eq!(decoded.to_snapshot_bytes(), bytes);
}
#[test]
fn test_non_ascending_loci_are_refused() {
let mut bytes = Vec::new();
bytes.push(0x01u8);
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&2u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&9u64.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&rank(100, 0).to_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&2u64.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&rank(200, 0).to_bytes());
empty_suffix(&mut bytes);
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::NonAscendingLoci {
previous_station: 1,
previous_index: 9,
found_station: 1,
found_index: 2
})
);
}
#[test]
fn test_a_run_overlapping_a_free_locus_is_refused() {
let mut bytes = valid_run_frame();
let suffix_at = bytes.len() - 5;
bytes.truncate(suffix_at);
bytes[5..9].copy_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&5u64.to_be_bytes());
bytes.push(0x00);
bytes.extend_from_slice(&rank(999, 0).to_bytes());
empty_suffix(&mut bytes);
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::NonAscendingLoci {
previous_station: 1,
previous_index: 5,
found_station: 1,
found_index: 5
})
);
}
#[test]
fn test_a_visible_dot_without_a_locus_is_refused() {
let mut bytes = valid_run_frame();
bytes.truncate(bytes.len() - 5);
let mut visible = DotSet::new();
assert!(visible.insert(d(9, 1)));
bytes.extend_from_slice(&visible.to_bytes());
assert_eq!(
Rhapsody::from_snapshot_bytes(&bytes),
Err(SnapshotDecodeError::VisibleWithoutLocus {
station: 9,
index: 1
})
);
}
#[test]
fn test_a_dangling_anchor_stays_accepted() {
let mut bytes = Vec::new();
bytes.push(0x01u8);
bytes.extend_from_slice(&0u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&1u32.to_be_bytes());
bytes.extend_from_slice(&7u64.to_be_bytes());
bytes.push(0x01);
bytes.extend_from_slice(&6u32.to_be_bytes());
bytes.extend_from_slice(&123u64.to_be_bytes());
bytes.extend_from_slice(&rank(100, 0).to_bytes());
empty_suffix(&mut bytes);
let decoded = Rhapsody::from_snapshot_bytes(&bytes).expect("a dangling anchor is legal");
assert_eq!(decoded.to_snapshot_bytes(), bytes);
}