use std::sync::Arc;
use structured_asn1::memory::{Memory, MemoryAccount, MemoryError, SortError, Work};
use structured_asn1::{Encoding, Limits, Parser};
#[test]
fn joint_admission_keeps_three_sources_and_one_account_bill() {
let account = Memory::new(Arc::new(MemoryAccount::new(0, 100)));
let limits = Limits {
max_work: 10,
..Limits::default()
};
let root = Parser::new(Encoding::Der, limits, account.clone());
let left = Parser::new(Encoding::Der, limits, account.clone());
let right = Parser::new(Encoding::Der, limits, account.clone());
let operation = root.execution_in(&account);
let pair = operation.execution_for(&left);
pair.charge_work_from([&right, &right], 4).unwrap();
assert_eq!(account.work_used(), 4);
for parser in [&root, &left, &right] {
assert_eq!(parser.execution_in(&account).work_remaining(), 6);
}
pair.charge_work_from([&root, &root], 2).unwrap();
assert_eq!(account.work_used(), 6);
assert_eq!(root.execution_in(&account).work_remaining(), 4);
assert_eq!(left.execution_in(&account).work_remaining(), 4);
assert_eq!(right.execution_in(&account).work_remaining(), 6);
right.execution_in(&account).charge_work(6).unwrap();
let before = account.work_used();
assert_eq!(
pair.charge_work_from([&right, &right], 1),
Err(MemoryError::WorkExceeded)
);
assert_eq!(account.work_used(), before);
assert_eq!(root.execution_in(&account).work_remaining(), 4);
assert_eq!(left.execution_in(&account).work_remaining(), 4);
}
#[test]
fn joint_admission_deduplicates_all_four_source_aliases() {
for left in 0..4 {
for right in 0..4 {
let account = Memory::new(Arc::new(MemoryAccount::new(0, 100)));
let parsers: [Parser; 4] = core::array::from_fn(|_| {
Parser::new(
Encoding::Der,
Limits {
max_work: 10,
..Limits::default()
},
account.clone(),
)
});
let root = parsers[0].execution_in(&account);
let current = root.execution_for(&parsers[1]);
current
.charge_work_from([&parsers[left], &parsers[right]], 5)
.unwrap();
assert_eq!(account.work_used(), 5);
for (index, parser) in parsers.iter().enumerate() {
let expected = if index < 2 || index == left || index == right {
5
} else {
10
};
assert_eq!(parser.execution_in(&account).work_remaining(), expected);
}
}
}
}
#[test]
fn scratch_growth_refuses_local_work_before_allocator_or_move() {
let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let parser = Parser::new(
Encoding::Ber,
Limits {
max_work: 8,
..Limits::default()
},
memory.clone(),
);
let reader = parser.read(&[], 900).unwrap();
let execution = reader.execution();
let mut bytes = execution.vector();
bytes.try_reserve_exact(4).unwrap();
bytes.try_push(7_u8).unwrap();
let work = memory.work_used();
assert_eq!(bytes.try_reserve_exact(8), Err(MemoryError::WorkExceeded));
assert_eq!(&*bytes, &[7]);
assert_eq!(bytes.capacity(), 4);
assert_eq!(memory.used(), 4);
assert_eq!(memory.work_used(), work);
drop(bytes);
assert_eq!(memory.used(), 0);
}
#[test]
fn scratch_sort_refuses_before_comparator_dispatch() {
let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let parser = Parser::new(
Encoding::Ber,
Limits {
max_work: 20,
..Limits::default()
},
memory.clone(),
);
let reader = parser.read(&[], 900).unwrap();
let execution = reader.execution();
let mut values = execution.vector();
values.try_reserve_exact(4).unwrap();
for value in [4_u32, 3, 2, 1] {
values.try_push(value).unwrap();
}
let mut comparisons = 0;
let result = values.try_sort_unstable_by(|left, right| {
comparisons += 1;
Ok::<_, MemoryError>(left.cmp(right))
});
assert_eq!(result, Err(SortError::Resource(MemoryError::WorkExceeded)));
assert_eq!(comparisons, 0);
assert_eq!(&*values, &[4, 3, 2, 1]);
drop(values);
assert_eq!(memory.used(), 0);
}
#[test]
fn scratch_execution_charges_aggregate_once_and_keeps_session_cumulative() {
let old = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let current = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let parser = Parser::new(
Encoding::Ber,
Limits {
max_work: 10,
..Limits::default()
},
old.clone(),
);
let reader = parser.read(&[], 900).unwrap().with_execution(¤t);
let execution = reader.execution();
let mut bytes = execution.vector();
bytes.try_reserve_exact(8).unwrap();
bytes.try_push(7_u8).unwrap();
assert_eq!(current.work_used(), 9);
assert_eq!(old.work_used(), 0);
reader.charge(1).unwrap();
assert_eq!(bytes.try_push(8), Err(MemoryError::WorkExceeded));
assert_eq!(&*bytes, &[7]);
assert_eq!(current.work_used(), 10);
drop(bytes);
assert_eq!(current.used(), 0);
}
#[test]
fn reader_descent_cannot_allocate_a_path_after_local_work_exhaustion() {
let bytes = [0x30, 0];
let probe = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let probe_parser = Parser::ber(probe.clone());
let mut probe_reader = probe_parser.read(&bytes, 400).unwrap();
probe_reader.next_element().unwrap().unwrap();
let header_work = probe.work_used() as u64;
let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
let parser = Parser::new(
Encoding::Ber,
Limits {
max_work: header_work,
..Limits::default()
},
memory.clone(),
);
let mut reader = parser.read(&bytes, 400).unwrap();
let element = reader.next_element().unwrap().unwrap();
let error = reader.enter(&element).unwrap_err();
assert!(error.is_resource());
assert_eq!(error.offset(), 402);
assert_eq!(memory.used(), 0);
assert_eq!(memory.work_used() as u64, header_work);
}
#[test]
fn forgotten_parser_scratch_keeps_its_allocation_account_alive() {
let account = Arc::new(MemoryAccount::new(1024, 1024));
let weak = Arc::downgrade(&account);
{
let parser = Parser::ber(Memory::new(account));
let reader = parser.read(&[], 0).unwrap();
let mut scratch = reader.execution().vector();
scratch.try_reserve_exact(4).unwrap();
scratch.try_push(7_u8).unwrap();
assert_eq!(reader.memory().used(), 4);
std::mem::forget(scratch);
}
let account = weak
.upgrade()
.expect("forgotten allocation retains its account");
let memory = Memory::new(account);
assert_eq!(memory.used(), 4);
}