use crate::CryptoError;
use crate::error::sanitize_for_display;
use super::reasons::{TOTAL_ENTRY_EXT_BYTES_OVERFLOW, TOTAL_FILE_BYTES_OVERFLOW};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct ArchiveLimits {
pub(crate) max_entry_count: u32,
pub(crate) max_total_plaintext_bytes: u64,
pub(crate) max_path_depth: u32,
pub(crate) max_path_bytes: u32,
pub(crate) max_manifest_bytes: u32,
pub(crate) max_archive_ext_bytes: u32,
pub(crate) max_entry_ext_bytes: u32,
pub(crate) max_total_entry_ext_bytes: u64,
pub(crate) max_tlv_value_bytes: u32,
}
impl ArchiveLimits {
pub const PATH_BYTES_STRUCTURAL_MAX: u32 = u16::MAX as u32;
pub const ENTRY_COUNT_DEFAULT: u32 = 250_000;
pub const TOTAL_PLAINTEXT_BYTES_DEFAULT: u64 = 64 * 1024 * 1024 * 1024;
pub const PATH_DEPTH_DEFAULT: u32 = 64;
pub const PATH_BYTES_DEFAULT: u32 = 4096;
pub const MANIFEST_BYTES_DEFAULT: u32 = 64 * 1024 * 1024;
pub const ARCHIVE_EXT_BYTES_DEFAULT: u32 = 64 * 1024;
pub const ENTRY_EXT_BYTES_DEFAULT: u32 = 64 * 1024;
pub const TOTAL_ENTRY_EXT_BYTES_DEFAULT: u64 = 64 * 1024 * 1024;
pub const TLV_VALUE_BYTES_DEFAULT: u32 = 16 * 1024 * 1024;
pub fn max_entry_count(mut self, value: u32) -> Self {
self.max_entry_count = value;
self
}
pub fn max_total_plaintext_bytes(mut self, value: u64) -> Self {
self.max_total_plaintext_bytes = value;
self
}
pub fn max_path_depth(mut self, value: u32) -> Self {
self.max_path_depth = value;
self
}
pub fn max_path_bytes(mut self, value: u32) -> Self {
self.max_path_bytes = value.min(Self::PATH_BYTES_STRUCTURAL_MAX);
self
}
pub fn max_manifest_bytes(mut self, value: u32) -> Self {
self.max_manifest_bytes = value;
self
}
pub fn max_archive_ext_bytes(mut self, value: u32) -> Self {
self.max_archive_ext_bytes = value;
self
}
pub fn max_entry_ext_bytes(mut self, value: u32) -> Self {
self.max_entry_ext_bytes = value;
self
}
pub fn max_total_entry_ext_bytes(mut self, value: u64) -> Self {
self.max_total_entry_ext_bytes = value;
self
}
pub fn max_tlv_value_bytes(mut self, value: u32) -> Self {
self.max_tlv_value_bytes = value;
self
}
}
const _: () =
assert!(ArchiveLimits::PATH_BYTES_DEFAULT <= ArchiveLimits::PATH_BYTES_STRUCTURAL_MAX);
impl Default for ArchiveLimits {
fn default() -> Self {
Self {
max_entry_count: Self::ENTRY_COUNT_DEFAULT,
max_total_plaintext_bytes: Self::TOTAL_PLAINTEXT_BYTES_DEFAULT,
max_path_depth: Self::PATH_DEPTH_DEFAULT,
max_path_bytes: Self::PATH_BYTES_DEFAULT,
max_manifest_bytes: Self::MANIFEST_BYTES_DEFAULT,
max_archive_ext_bytes: Self::ARCHIVE_EXT_BYTES_DEFAULT,
max_entry_ext_bytes: Self::ENTRY_EXT_BYTES_DEFAULT,
max_total_entry_ext_bytes: Self::TOTAL_ENTRY_EXT_BYTES_DEFAULT,
max_tlv_value_bytes: Self::TLV_VALUE_BYTES_DEFAULT,
}
}
}
pub(crate) fn enforce_per_entry_caps(
entry_count: u32,
path_utf8: &str,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
enforce_entry_count_cap(entry_count, limits)?;
enforce_path_depth_cap(path_utf8, limits)?;
Ok(())
}
pub(crate) fn enforce_entry_count_cap(
entry_count: u32,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if entry_count > limits.max_entry_count {
return Err(entry_count_cap_error(entry_count, limits.max_entry_count));
}
Ok(())
}
pub(super) fn component_count(path: &str) -> usize {
path.split('/').count()
}
pub(crate) fn enforce_path_depth_cap(
path_utf8: &str,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
let depth = u32::try_from(component_count(path_utf8)).unwrap_or(u32::MAX);
if depth > limits.max_path_depth {
return Err(path_depth_cap_error(
depth,
limits.max_path_depth,
path_utf8,
));
}
Ok(())
}
pub(crate) fn enforce_path_bytes_cap(
path_len: u32,
path: Option<&str>,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if path_len > limits.max_path_bytes {
return Err(path_bytes_cap_error(path_len, limits.max_path_bytes, path));
}
Ok(())
}
pub(crate) fn enforce_manifest_len_cap(
manifest_len: u64,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if manifest_len > u64::from(limits.max_manifest_bytes) {
return Err(manifest_len_cap_error(
manifest_len,
limits.max_manifest_bytes,
));
}
Ok(())
}
pub(crate) fn enforce_archive_ext_cap(
archive_ext_len: u64,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if archive_ext_len > u64::from(limits.max_archive_ext_bytes) {
return Err(archive_ext_cap_error(
archive_ext_len,
limits.max_archive_ext_bytes,
));
}
Ok(())
}
pub(crate) fn enforce_total_plaintext_bytes_cap(
total_file_bytes: u64,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if total_file_bytes > limits.max_total_plaintext_bytes {
return Err(total_bytes_cap_error(
total_file_bytes,
limits.max_total_plaintext_bytes,
));
}
Ok(())
}
pub(crate) fn enforce_total_bytes_cap(
entry_size: u64,
total_bytes: &mut u64,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
let next = total_bytes
.checked_add(entry_size)
.ok_or(CryptoError::MalformedArchive {
reason: TOTAL_FILE_BYTES_OVERFLOW,
})?;
if next > limits.max_total_plaintext_bytes {
return Err(total_bytes_cap_error(
next,
limits.max_total_plaintext_bytes,
));
}
*total_bytes = next;
Ok(())
}
pub(crate) fn enforce_entry_ext_cap(
entry_ext_len: u64,
path: Option<&str>,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
if entry_ext_len > u64::from(limits.max_entry_ext_bytes) {
return Err(entry_ext_cap_error(
entry_ext_len,
limits.max_entry_ext_bytes,
path,
));
}
Ok(())
}
pub(crate) fn enforce_total_entry_ext_cap(
entry_ext_len: u64,
total: &mut u64,
limits: &ArchiveLimits,
) -> Result<(), CryptoError> {
let next = total
.checked_add(entry_ext_len)
.ok_or(CryptoError::MalformedArchive {
reason: TOTAL_ENTRY_EXT_BYTES_OVERFLOW,
})?;
if next > limits.max_total_entry_ext_bytes {
return Err(total_entry_ext_cap_error(
next,
limits.max_total_entry_ext_bytes,
));
}
*total = next;
Ok(())
}
pub(super) fn entry_count_cap_error(entry_count: u32, cap: u32) -> CryptoError {
CryptoError::ArchiveEntryCountCapExceeded {
entry_count,
local_cap: cap,
}
}
pub(super) fn total_bytes_cap_error(total: u64, cap: u64) -> CryptoError {
CryptoError::ArchiveTotalBytesCapExceeded {
total_bytes: total,
local_cap: cap,
}
}
pub(super) fn manifest_len_cap_error(len: u64, cap: u32) -> CryptoError {
CryptoError::ArchiveManifestLenCapExceeded {
manifest_len: len,
local_cap: cap,
}
}
pub(super) fn path_bytes_cap_error(declared_len: u32, cap: u32, path: Option<&str>) -> CryptoError {
CryptoError::ArchivePathBytesCapExceeded {
path_bytes: declared_len,
local_cap: cap,
path: path.map(sanitize_for_display),
}
}
pub(super) fn path_depth_cap_error(depth: u32, cap: u32, path_utf8: &str) -> CryptoError {
CryptoError::ArchivePathDepthCapExceeded {
depth,
local_cap: cap,
path: sanitize_for_display(path_utf8),
}
}
pub(super) fn archive_ext_cap_error(declared_len: u64, cap: u32) -> CryptoError {
CryptoError::ArchiveExtLenCapExceeded {
ext_len: declared_len,
local_cap: cap,
}
}
pub(super) fn entry_ext_cap_error(declared_len: u64, cap: u32, path: Option<&str>) -> CryptoError {
CryptoError::ArchiveEntryExtLenCapExceeded {
ext_len: declared_len,
local_cap: cap,
path: path.map(sanitize_for_display),
}
}
pub(super) fn total_entry_ext_cap_error(total: u64, cap: u64) -> CryptoError {
CryptoError::ArchiveTotalEntryExtCapExceeded {
total_ext_bytes: total,
local_cap: cap,
}
}
#[cfg(test)]
mod tests {
use super::{
ArchiveLimits, enforce_archive_ext_cap, enforce_entry_ext_cap, enforce_manifest_len_cap,
enforce_path_bytes_cap, enforce_per_entry_caps, enforce_total_bytes_cap,
enforce_total_entry_ext_cap,
};
#[test]
fn every_cap_is_routed_into_a_test_that_exercises_it() {
let ArchiveLimits {
max_entry_count: _,
max_total_plaintext_bytes: _,
max_path_depth: _,
max_path_bytes: _,
max_manifest_bytes: _,
max_archive_ext_bytes: _,
max_entry_ext_bytes: _,
max_total_entry_ext_bytes: _,
max_tlv_value_bytes: _,
} = ArchiveLimits::default();
}
#[test]
fn defaults_match_spec_values() {
let l = ArchiveLimits::default();
assert_eq!(l.max_entry_count, ArchiveLimits::ENTRY_COUNT_DEFAULT);
assert_eq!(l.max_entry_count, 250_000);
assert_eq!(
l.max_total_plaintext_bytes,
ArchiveLimits::TOTAL_PLAINTEXT_BYTES_DEFAULT
);
assert_eq!(l.max_total_plaintext_bytes, 64 * 1024 * 1024 * 1024);
assert_eq!(l.max_path_depth, ArchiveLimits::PATH_DEPTH_DEFAULT);
assert_eq!(l.max_path_depth, 64);
assert_eq!(l.max_path_bytes, ArchiveLimits::PATH_BYTES_DEFAULT);
assert_eq!(l.max_path_bytes, 4096);
assert_eq!(l.max_manifest_bytes, ArchiveLimits::MANIFEST_BYTES_DEFAULT);
assert_eq!(l.max_manifest_bytes, 64 * 1024 * 1024);
assert_eq!(
l.max_archive_ext_bytes,
ArchiveLimits::ARCHIVE_EXT_BYTES_DEFAULT
);
assert_eq!(l.max_archive_ext_bytes, 64 * 1024);
assert_eq!(
l.max_entry_ext_bytes,
ArchiveLimits::ENTRY_EXT_BYTES_DEFAULT
);
assert_eq!(l.max_entry_ext_bytes, 64 * 1024);
assert_eq!(
l.max_total_entry_ext_bytes,
ArchiveLimits::TOTAL_ENTRY_EXT_BYTES_DEFAULT
);
assert_eq!(l.max_total_entry_ext_bytes, 64 * 1024 * 1024);
assert_eq!(
l.max_tlv_value_bytes,
ArchiveLimits::TLV_VALUE_BYTES_DEFAULT
);
assert_eq!(l.max_tlv_value_bytes, 16 * 1024 * 1024);
}
#[test]
fn path_bytes_builder_clamps_at_structural_max() {
let at = ArchiveLimits::default().max_path_bytes(ArchiveLimits::PATH_BYTES_STRUCTURAL_MAX);
assert_eq!(at.max_path_bytes, ArchiveLimits::PATH_BYTES_STRUCTURAL_MAX);
let above = ArchiveLimits::default().max_path_bytes(u32::MAX);
assert_eq!(
above.max_path_bytes,
ArchiveLimits::PATH_BYTES_STRUCTURAL_MAX
);
}
#[test]
fn builders_replace_only_targeted_field() {
let base = ArchiveLimits::default();
let mut want = base;
want.max_entry_count = 7;
assert_eq!(base.max_entry_count(7), want);
let mut want = base;
want.max_total_plaintext_bytes = 123;
assert_eq!(base.max_total_plaintext_bytes(123), want);
let mut want = base;
want.max_path_depth = 7;
assert_eq!(base.max_path_depth(7), want);
let mut want = base;
want.max_path_bytes = 99;
assert_eq!(base.max_path_bytes(99), want);
let mut want = base;
want.max_manifest_bytes = 42;
assert_eq!(base.max_manifest_bytes(42), want);
let mut want = base;
want.max_archive_ext_bytes = 1234;
assert_eq!(base.max_archive_ext_bytes(1234), want);
let mut want = base;
want.max_entry_ext_bytes = 5678;
assert_eq!(base.max_entry_ext_bytes(5678), want);
let mut want = base;
want.max_total_entry_ext_bytes = 999;
assert_eq!(base.max_total_entry_ext_bytes(999), want);
let mut want = base;
want.max_tlv_value_bytes = 321;
assert_eq!(base.max_tlv_value_bytes(321), want);
}
#[test]
fn enforce_per_entry_caps_entry_count_boundary() {
let limits = ArchiveLimits::default().max_entry_count(10);
assert!(enforce_per_entry_caps(10, "a", &limits).is_ok());
assert!(enforce_per_entry_caps(11, "a", &limits).is_err());
}
#[test]
fn enforce_per_entry_caps_depth_boundary() {
let limits = ArchiveLimits::default().max_path_depth(3);
assert!(enforce_per_entry_caps(1, "a/b/c", &limits).is_ok());
assert!(enforce_per_entry_caps(1, "a/b/c/d", &limits).is_err());
}
#[test]
fn enforce_total_bytes_cap_rejects_overflow() {
let limits = ArchiveLimits::default().max_total_plaintext_bytes(u64::MAX);
let mut total = u64::MAX - 100;
let result = enforce_total_bytes_cap(200, &mut total, &limits);
assert!(result.is_err());
assert_eq!(
total,
u64::MAX - 100,
"total_bytes must not wrap or saturate"
);
}
#[test]
fn enforce_total_bytes_cap_boundary() {
let limits = ArchiveLimits::default().max_total_plaintext_bytes(100);
let mut total = 0;
assert!(enforce_total_bytes_cap(100, &mut total, &limits).is_ok());
assert_eq!(total, 100);
assert!(enforce_total_bytes_cap(1, &mut total, &limits).is_err());
assert_eq!(total, 100);
}
#[test]
fn enforce_path_bytes_cap_boundary() {
let limits = ArchiveLimits::default().max_path_bytes(10);
assert!(enforce_path_bytes_cap(10, None, &limits).is_ok());
assert!(enforce_path_bytes_cap(11, None, &limits).is_err());
}
#[test]
fn enforce_manifest_len_cap_boundary() {
let limits = ArchiveLimits::default().max_manifest_bytes(100);
assert!(enforce_manifest_len_cap(100, &limits).is_ok());
assert!(enforce_manifest_len_cap(101, &limits).is_err());
}
#[test]
fn enforce_archive_ext_cap_boundary() {
let limits = ArchiveLimits::default().max_archive_ext_bytes(100);
assert!(enforce_archive_ext_cap(100, &limits).is_ok());
assert!(enforce_archive_ext_cap(101, &limits).is_err());
}
#[test]
fn enforce_entry_ext_cap_boundary() {
let limits = ArchiveLimits::default().max_entry_ext_bytes(100);
assert!(enforce_entry_ext_cap(100, None, &limits).is_ok());
assert!(enforce_entry_ext_cap(101, None, &limits).is_err());
}
#[test]
fn enforce_total_entry_ext_cap_boundary() {
let limits = ArchiveLimits::default().max_total_entry_ext_bytes(100);
let mut total = 0;
assert!(enforce_total_entry_ext_cap(100, &mut total, &limits).is_ok());
assert_eq!(total, 100);
assert!(enforce_total_entry_ext_cap(1, &mut total, &limits).is_err());
assert_eq!(total, 100);
}
}