use crate::{
emulation::{engine::synthetic_exception, runtime::hook::PreHookResult},
utils::MAX_DERIVED_KEY_LEN,
};
pub(crate) const MAX_HOOK_BUFFER: usize = 16 * 1024 * 1024;
pub(crate) const MAX_HOOK_STRING_CHARS: usize = 4 * 1024 * 1024;
pub(crate) const MAX_DERIVED_KEY_BYTES: usize = MAX_DERIVED_KEY_LEN;
pub(crate) const MAX_KDF_ITERATIONS: u32 = 100_000;
pub(crate) fn negative_argument(method: &str, param: &str) -> PreHookResult {
PreHookResult::Throw {
exception_type: synthetic_exception::ARGUMENT_OUT_OF_RANGE,
message: format!("{method}: '{param}' must be non-negative"),
}
}
pub(crate) fn oversized_argument(
method: &str,
param: &str,
requested: usize,
max: usize,
) -> PreHookResult {
PreHookResult::Throw {
exception_type: synthetic_exception::OUT_OF_MEMORY,
message: format!(
"{method}: '{param}' of {requested} exceeds the emulator's per-call limit of {max}"
),
}
}
#[allow(clippy::result_large_err)]
pub(crate) fn checked_len<T>(
value: T,
max: usize,
method: &str,
param: &str,
) -> Result<usize, PreHookResult>
where
usize: TryFrom<T>,
{
let Ok(len) = usize::try_from(value) else {
return Err(negative_argument(method, param));
};
if len > max {
return Err(oversized_argument(method, param, len, max));
}
Ok(len)
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_throws(result: &PreHookResult, expected: crate::metadata::token::Token) {
match result {
PreHookResult::Throw { exception_type, .. } => assert_eq!(*exception_type, expected),
other => panic!("expected a throw, got {other:?}"),
}
}
#[test]
fn accepts_a_reasonable_count() {
assert_eq!(
checked_len(1024_i32, MAX_HOOK_BUFFER, "M", "count").unwrap(),
1024
);
}
#[test]
fn accepts_zero() {
assert_eq!(
checked_len(0_i32, MAX_HOOK_BUFFER, "M", "count").unwrap(),
0
);
}
#[test]
fn rejects_negative_i32_as_argument_out_of_range() {
let err = checked_len(-1_i32, MAX_HOOK_BUFFER, "M", "count").unwrap_err();
assert_throws(&err, synthetic_exception::ARGUMENT_OUT_OF_RANGE);
}
#[test]
fn rejects_negative_i64_as_argument_out_of_range() {
let err = checked_len(i64::MIN, MAX_HOOK_BUFFER, "M", "count").unwrap_err();
assert_throws(&err, synthetic_exception::ARGUMENT_OUT_OF_RANGE);
}
#[test]
fn rejects_oversized_count_as_out_of_memory() {
let err = checked_len(i32::MAX, 1024, "M", "count").unwrap_err();
assert_throws(&err, synthetic_exception::OUT_OF_MEMORY);
}
#[test]
fn accepts_exactly_the_ceiling() {
assert_eq!(checked_len(1024_i32, 1024, "M", "count").unwrap(), 1024);
}
#[test]
fn message_names_the_method_and_parameter() {
let err = checked_len(-5_i32, MAX_HOOK_BUFFER, "String.PadLeft", "totalWidth").unwrap_err();
match err {
PreHookResult::Throw { message, .. } => {
assert!(message.contains("String.PadLeft"), "got: {message}");
assert!(message.contains("totalWidth"), "got: {message}");
}
other => panic!("expected a throw, got {other:?}"),
}
}
}