minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec::Vec;

use super::super::super::super::support::dot;
use crate::metis::{DotSet, HaveSetDecodeBudget, HaveSetDecodeError};

#[test]
fn test_have_set_from_bytes_rejects_zero_length_run() {
    // Station 1, floor 2, one run at start 5 with len 0: no encoder emits a
    // zero-length run.
    let mut frame = Vec::new();
    frame.push(0x01u8);
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes()); // station 1
    frame.extend_from_slice(&2u64.to_be_bytes()); // floor 2
    frame.extend_from_slice(&1u32.to_be_bytes()); // one run
    frame.extend_from_slice(&5u64.to_be_bytes()); // start 5
    frame.extend_from_slice(&0u64.to_be_bytes()); // len 0
    assert_eq!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::ZeroLengthRun { station: 1 })
    );
}

#[test]
fn test_have_set_from_bytes_rejects_run_adjacent_to_floor() {
    // Station 1, floor 3, a run starting at floor + 1 = 4: absorbable, so
    // non-maximal (an encoder folds dot 4 into the floor).
    let mut frame = Vec::new();
    frame.push(0x01u8);
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes()); // station 1
    frame.extend_from_slice(&3u64.to_be_bytes()); // floor 3
    frame.extend_from_slice(&1u32.to_be_bytes()); // one run
    frame.extend_from_slice(&4u64.to_be_bytes()); // start 4 == floor + 1
    frame.extend_from_slice(&1u64.to_be_bytes()); // len 1
    assert_eq!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::NonMaximalRun {
            station: 1,
            start: 4,
        })
    );
}

#[test]
fn test_have_set_from_bytes_rejects_adjacent_runs() {
    // Station 1, floor 2, run (5, 1) then run (6, 1): the second starts at the
    // first's end + 1, so the two are absorbable into one (6 == 5 + 1). An encoder
    // emits a single run (5, 2).
    let mut frame = Vec::new();
    frame.push(0x01u8);
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes()); // station 1
    frame.extend_from_slice(&2u64.to_be_bytes()); // floor 2
    frame.extend_from_slice(&2u32.to_be_bytes()); // two runs
    frame.extend_from_slice(&5u64.to_be_bytes()); // run 1 start 5
    frame.extend_from_slice(&1u64.to_be_bytes()); // run 1 len 1 (ends at 5)
    frame.extend_from_slice(&6u64.to_be_bytes()); // run 2 start 6 == 5 + 1
    frame.extend_from_slice(&1u64.to_be_bytes()); // run 2 len 1
    assert_eq!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::NonMaximalRun {
            station: 1,
            start: 6,
        })
    );
}

#[test]
fn test_have_set_from_bytes_rejects_run_too_long() {
    // Station 1, floor 0, one run at start 2 with len 1_000_000: the run would
    // materialize a million dots from a ~33-byte frame, far past the input-length
    // dot budget, so it is refused rather than decompressed at the trust boundary.
    let mut frame = Vec::new();
    frame.push(0x01u8);
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes()); // station 1
    frame.extend_from_slice(&0u64.to_be_bytes()); // floor 0
    frame.extend_from_slice(&1u32.to_be_bytes()); // one run
    frame.extend_from_slice(&2u64.to_be_bytes()); // start 2 (> floor + 1)
    frame.extend_from_slice(&1_000_000u64.to_be_bytes()); // len 1_000_000
    assert_eq!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::RunTooLong {
            station: 1,
            start: 2,
            len: 1_000_000,
        })
    );
}

#[test]
fn test_have_set_from_bytes_accepts_dense_run_under_explicit_budget() {
    let mut set = DotSet::new();
    for counter in 2..=100 {
        assert!(set.insert(dot(1, counter)));
    }
    let frame = set.to_bytes();
    assert!(frame.len() < set.exceptions_len());
    assert!(matches!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::RunTooLong { .. })
    ));
    assert!(matches!(
        DotSet::from_bytes_with_budget(&frame, HaveSetDecodeBudget::new(98)),
        Err(HaveSetDecodeError::RunTooLong { .. })
    ));
    assert_eq!(
        DotSet::from_bytes_with_budget(&frame, HaveSetDecodeBudget::new(99)),
        Ok(set)
    );
}

#[test]
fn test_zero_budget_accepts_only_floor_covered_dots() {
    let mut floor_only = DotSet::new();
    assert!(floor_only.insert(dot(1, 1)));
    assert!(floor_only.insert(dot(1, 2)));
    let zero = HaveSetDecodeBudget::new(0);
    assert_eq!(
        DotSet::from_bytes_with_budget(&floor_only.to_bytes(), zero),
        Ok(floor_only)
    );

    let mut one_exception = DotSet::new();
    assert!(one_exception.insert(dot(1, 2)));
    assert!(matches!(
        DotSet::from_bytes_with_budget(&one_exception.to_bytes(), zero),
        Err(HaveSetDecodeError::RunTooLong { .. })
    ));
}

#[test]
fn test_decode_budget_is_shared_across_stations() {
    let mut set = DotSet::new();
    for station in [1, 2] {
        assert!(set.insert(dot(station, 2)));
        assert!(set.insert(dot(station, 3)));
    }
    let frame = set.to_bytes();
    assert!(matches!(
        DotSet::from_bytes_with_budget(&frame, HaveSetDecodeBudget::new(3)),
        Err(HaveSetDecodeError::RunTooLong {
            station: 2,
            start: 2,
            len: 2,
        })
    ));
    assert_eq!(
        DotSet::from_bytes_with_budget(&frame, HaveSetDecodeBudget::new(4)),
        Ok(set)
    );
}

#[test]
fn test_have_set_from_bytes_rejects_run_overflow() {
    // Station 1, floor 2, a run at start u64::MAX with len 2: the end
    // (start + len - 1) overflows u64, naming an unrepresentable dot.
    let mut frame = Vec::new();
    frame.push(0x01u8);
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes()); // station 1
    frame.extend_from_slice(&2u64.to_be_bytes()); // floor 2
    frame.extend_from_slice(&1u32.to_be_bytes()); // one run
    frame.extend_from_slice(&u64::MAX.to_be_bytes()); // start u64::MAX
    frame.extend_from_slice(&2u64.to_be_bytes()); // len 2 -> end overflows
    assert_eq!(
        DotSet::from_bytes(&frame),
        Err(HaveSetDecodeError::RunOverflow {
            station: 1,
            start: u64::MAX,
            len: 2,
        })
    );
}