1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ThreadingPolicy {
16 SingleThread,
20
21 LimitOrSingle {
24 max_threads: u16,
26 },
27
28 LimitOrAny {
31 preferred_max_threads: u16,
33 },
34
35 Balanced,
38
39 #[default]
41 Unlimited,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70#[non_exhaustive]
71pub struct ResourceLimits {
72 pub max_pixels: Option<u64>,
74 pub max_memory_bytes: Option<u64>,
76 pub max_output_bytes: Option<u64>,
78 pub max_width: Option<u32>,
80 pub max_height: Option<u32>,
82 pub max_input_bytes: Option<u64>,
84 pub max_frames: Option<u32>,
86 pub max_animation_ms: Option<u64>,
88 pub threading: ThreadingPolicy,
92}
93
94#[cfg(target_pointer_width = "64")]
97const _: () = assert!(core::mem::size_of::<ResourceLimits>() == 112);
98
99impl Default for ResourceLimits {
100 fn default() -> Self {
101 Self {
102 max_pixels: None,
103 max_memory_bytes: None,
104 max_output_bytes: None,
105 max_width: None,
106 max_height: None,
107 max_input_bytes: None,
108 max_frames: None,
109 max_animation_ms: None,
110 threading: ThreadingPolicy::Unlimited,
111 }
112 }
113}
114
115impl ResourceLimits {
116 pub fn none() -> Self {
118 Self::default()
119 }
120
121 pub fn with_max_pixels(mut self, max: u64) -> Self {
123 self.max_pixels = Some(max);
124 self
125 }
126
127 pub fn with_max_memory(mut self, bytes: u64) -> Self {
129 self.max_memory_bytes = Some(bytes);
130 self
131 }
132
133 pub fn with_max_output(mut self, bytes: u64) -> Self {
135 self.max_output_bytes = Some(bytes);
136 self
137 }
138
139 pub fn with_max_width(mut self, width: u32) -> Self {
141 self.max_width = Some(width);
142 self
143 }
144
145 pub fn with_max_height(mut self, height: u32) -> Self {
147 self.max_height = Some(height);
148 self
149 }
150
151 pub fn with_max_input_bytes(mut self, bytes: u64) -> Self {
153 self.max_input_bytes = Some(bytes);
154 self
155 }
156
157 pub fn with_max_frames(mut self, frames: u32) -> Self {
159 self.max_frames = Some(frames);
160 self
161 }
162
163 pub fn with_max_animation_ms(mut self, ms: u64) -> Self {
165 self.max_animation_ms = Some(ms);
166 self
167 }
168
169 pub fn with_threading(mut self, policy: ThreadingPolicy) -> Self {
171 self.threading = policy;
172 self
173 }
174
175 pub fn threading(&self) -> ThreadingPolicy {
177 self.threading
178 }
179
180 pub fn has_any(&self) -> bool {
182 self.max_pixels.is_some()
183 || self.max_memory_bytes.is_some()
184 || self.max_output_bytes.is_some()
185 || self.max_width.is_some()
186 || self.max_height.is_some()
187 || self.max_input_bytes.is_some()
188 || self.max_frames.is_some()
189 || self.max_animation_ms.is_some()
190 || self.threading != ThreadingPolicy::Unlimited
191 }
192
193 pub fn check_dimensions(&self, width: u32, height: u32) -> Result<(), LimitExceeded> {
197 if let Some(max) = self.max_width
198 && width > max
199 {
200 return Err(LimitExceeded::Width { actual: width, max });
201 }
202 if let Some(max) = self.max_height
203 && height > max
204 {
205 return Err(LimitExceeded::Height {
206 actual: height,
207 max,
208 });
209 }
210 if let Some(max) = self.max_pixels {
211 let pixels = width as u64 * height as u64;
212 if pixels > max {
213 return Err(LimitExceeded::Pixels {
214 actual: pixels,
215 max,
216 });
217 }
218 }
219 Ok(())
220 }
221
222 pub fn check_memory(&self, bytes: u64) -> Result<(), LimitExceeded> {
224 if let Some(max) = self.max_memory_bytes
225 && bytes > max
226 {
227 return Err(LimitExceeded::Memory { actual: bytes, max });
228 }
229 Ok(())
230 }
231
232 pub fn check_input_size(&self, bytes: u64) -> Result<(), LimitExceeded> {
234 if let Some(max) = self.max_input_bytes
235 && bytes > max
236 {
237 return Err(LimitExceeded::InputSize { actual: bytes, max });
238 }
239 Ok(())
240 }
241
242 pub fn check_output_size(&self, bytes: u64) -> Result<(), LimitExceeded> {
244 if let Some(max) = self.max_output_bytes
245 && bytes > max
246 {
247 return Err(LimitExceeded::OutputSize { actual: bytes, max });
248 }
249 Ok(())
250 }
251
252 pub fn check_frames(&self, count: u32) -> Result<(), LimitExceeded> {
254 if let Some(max) = self.max_frames
255 && count > max
256 {
257 return Err(LimitExceeded::Frames { actual: count, max });
258 }
259 Ok(())
260 }
261
262 pub fn check_animation_ms(&self, ms: u64) -> Result<(), LimitExceeded> {
264 if let Some(max) = self.max_animation_ms
265 && ms > max
266 {
267 return Err(LimitExceeded::Duration { actual: ms, max });
268 }
269 Ok(())
270 }
271
272 pub fn check_image_info(&self, info: &crate::ImageInfo) -> Result<(), LimitExceeded> {
278 self.check_dimensions(info.width, info.height)?;
279 if let Some(max) = self.max_frames
280 && let Some(count) = info.frame_count()
281 && count > max
282 {
283 return Err(LimitExceeded::Frames { actual: count, max });
284 }
285 Ok(())
286 }
287
288 pub fn check_output_info(&self, info: &crate::OutputInfo) -> Result<(), LimitExceeded> {
292 self.check_dimensions(info.width, info.height)
293 }
294}
295
296#[derive(Clone, Debug, PartialEq, Eq)]
305#[non_exhaustive]
306pub enum LimitExceeded {
307 Width {
309 actual: u32,
311 max: u32,
313 },
314 Height {
316 actual: u32,
318 max: u32,
320 },
321 Pixels {
323 actual: u64,
325 max: u64,
327 },
328 Memory {
330 actual: u64,
332 max: u64,
334 },
335 InputSize {
337 actual: u64,
339 max: u64,
341 },
342 OutputSize {
344 actual: u64,
346 max: u64,
348 },
349 Frames {
351 actual: u32,
353 max: u32,
355 },
356 Duration {
358 actual: u64,
360 max: u64,
362 },
363}
364
365impl core::fmt::Display for LimitExceeded {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 match self {
368 Self::Width { actual, max } => write!(f, "width {actual} exceeds limit {max}"),
369 Self::Height { actual, max } => write!(f, "height {actual} exceeds limit {max}"),
370 Self::Pixels { actual, max } => {
371 write!(f, "pixel count {actual} exceeds limit {max}")
372 }
373 Self::Memory { actual, max } => {
374 write!(f, "memory {actual} bytes exceeds limit {max}")
375 }
376 Self::InputSize { actual, max } => {
377 write!(f, "input size {actual} bytes exceeds limit {max}")
378 }
379 Self::OutputSize { actual, max } => {
380 write!(f, "output size {actual} bytes exceeds limit {max}")
381 }
382 Self::Frames { actual, max } => {
383 write!(f, "frame count {actual} exceeds limit {max}")
384 }
385 Self::Duration { actual, max } => {
386 write!(f, "duration {actual}ms exceeds limit {max}ms")
387 }
388 }
389 }
390}
391
392impl core::error::Error for LimitExceeded {}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn default_has_no_limits() {
400 let limits = ResourceLimits::none();
401 assert!(!limits.has_any());
402 }
403
404 #[test]
405 fn builder_sets_limits() {
406 let limits = ResourceLimits::none()
407 .with_max_pixels(1_000_000)
408 .with_max_memory(512 * 1024 * 1024);
409 assert!(limits.has_any());
410 assert_eq!(limits.max_pixels, Some(1_000_000));
411 assert_eq!(limits.max_memory_bytes, Some(512 * 1024 * 1024));
412 assert!(limits.max_output_bytes.is_none());
413 }
414
415 #[test]
416 fn animation_limits() {
417 let limits = ResourceLimits::none()
418 .with_max_frames(100)
419 .with_max_animation_ms(30_000);
420 assert!(limits.has_any());
421 assert_eq!(limits.max_frames, Some(100));
422 assert_eq!(limits.max_animation_ms, Some(30_000));
423 }
424
425 #[test]
426 fn has_any_includes_animation_fields() {
427 let limits = ResourceLimits::none().with_max_frames(10);
428 assert!(limits.has_any());
429
430 let limits = ResourceLimits::none().with_max_animation_ms(5000);
431 assert!(limits.has_any());
432 }
433
434 #[test]
435 fn threading_policy_default() {
436 let limits = ResourceLimits::none();
437 assert_eq!(limits.threading(), ThreadingPolicy::Unlimited);
438 assert!(!limits.has_any());
439 }
440
441 #[test]
442 fn threading_policy_single_thread() {
443 let limits = ResourceLimits::none().with_threading(ThreadingPolicy::SingleThread);
444 assert!(limits.has_any());
445 assert_eq!(limits.threading(), ThreadingPolicy::SingleThread);
446 }
447
448 #[test]
449 fn threading_policy_limit_or_single() {
450 let limits = ResourceLimits::none()
451 .with_threading(ThreadingPolicy::LimitOrSingle { max_threads: 4 });
452 assert!(limits.has_any());
453 assert_eq!(
454 limits.threading(),
455 ThreadingPolicy::LimitOrSingle { max_threads: 4 }
456 );
457 }
458
459 #[test]
460 fn threading_policy_balanced() {
461 let limits = ResourceLimits::none().with_threading(ThreadingPolicy::Balanced);
462 assert!(limits.has_any());
463 }
464
465 #[test]
468 fn check_dimensions_pass() {
469 let limits = ResourceLimits::none()
470 .with_max_width(1920)
471 .with_max_height(1080)
472 .with_max_pixels(2_073_600);
473 assert!(limits.check_dimensions(1920, 1080).is_ok());
474 assert!(limits.check_dimensions(100, 100).is_ok());
475 }
476
477 #[test]
478 fn check_dimensions_width_exceeded() {
479 let limits = ResourceLimits::none().with_max_width(1920);
480 let err = limits.check_dimensions(1921, 1080).unwrap_err();
481 assert_eq!(
482 err,
483 LimitExceeded::Width {
484 actual: 1921,
485 max: 1920
486 }
487 );
488 }
489
490 #[test]
491 fn check_dimensions_height_exceeded() {
492 let limits = ResourceLimits::none().with_max_height(1080);
493 let err = limits.check_dimensions(1920, 1081).unwrap_err();
494 assert_eq!(
495 err,
496 LimitExceeded::Height {
497 actual: 1081,
498 max: 1080
499 }
500 );
501 }
502
503 #[test]
504 fn check_dimensions_pixels_exceeded() {
505 let limits = ResourceLimits::none().with_max_pixels(1_000_000);
506 let err = limits.check_dimensions(1001, 1000).unwrap_err();
507 assert_eq!(
508 err,
509 LimitExceeded::Pixels {
510 actual: 1_001_000,
511 max: 1_000_000
512 }
513 );
514 }
515
516 #[test]
517 fn check_dimensions_no_limits_always_passes() {
518 let limits = ResourceLimits::none();
519 assert!(limits.check_dimensions(100_000, 100_000).is_ok());
520 }
521
522 #[test]
523 fn check_memory_pass_and_fail() {
524 let limits = ResourceLimits::none().with_max_memory(512 * 1024 * 1024);
525 assert!(limits.check_memory(256 * 1024 * 1024).is_ok());
526 let err = limits.check_memory(1024 * 1024 * 1024).unwrap_err();
527 assert!(matches!(err, LimitExceeded::Memory { .. }));
528 }
529
530 #[test]
531 fn check_input_size_pass_and_fail() {
532 let limits = ResourceLimits::none().with_max_input_bytes(10 * 1024 * 1024);
533 assert!(limits.check_input_size(5 * 1024 * 1024).is_ok());
534 let err = limits.check_input_size(20 * 1024 * 1024).unwrap_err();
535 assert!(matches!(err, LimitExceeded::InputSize { .. }));
536 }
537
538 #[test]
539 fn check_output_size_pass_and_fail() {
540 let limits = ResourceLimits::none().with_max_output(1024);
541 assert!(limits.check_output_size(512).is_ok());
542 let err = limits.check_output_size(2048).unwrap_err();
543 assert!(matches!(err, LimitExceeded::OutputSize { .. }));
544 }
545
546 #[test]
547 fn check_frames_pass_and_fail() {
548 let limits = ResourceLimits::none().with_max_frames(100);
549 assert!(limits.check_frames(50).is_ok());
550 let err = limits.check_frames(200).unwrap_err();
551 assert_eq!(
552 err,
553 LimitExceeded::Frames {
554 actual: 200,
555 max: 100
556 }
557 );
558 }
559
560 #[test]
561 fn check_animation_ms_pass_and_fail() {
562 let limits = ResourceLimits::none().with_max_animation_ms(30_000);
563 assert!(limits.check_animation_ms(15_000).is_ok());
564 let err = limits.check_animation_ms(60_000).unwrap_err();
565 assert!(matches!(err, LimitExceeded::Duration { .. }));
566 }
567
568 #[test]
569 fn check_image_info_dimensions_and_frames() {
570 use crate::{ImageFormat, ImageInfo};
571 let limits = ResourceLimits::none()
572 .with_max_width(4096)
573 .with_max_pixels(16_000_000)
574 .with_max_frames(100);
575
576 let info = ImageInfo::new(3840, 2160, ImageFormat::Avif).with_sequence(
577 crate::ImageSequence::Animation {
578 frame_count: Some(50),
579 loop_count: None,
580 random_access: false,
581 },
582 );
583 assert!(limits.check_image_info(&info).is_ok());
584
585 let big = ImageInfo::new(5000, 4000, ImageFormat::Jpeg);
586 let err = limits.check_image_info(&big).unwrap_err();
587 assert!(matches!(err, LimitExceeded::Width { .. }));
588
589 let many_frames = ImageInfo::new(100, 100, ImageFormat::Gif).with_sequence(
590 crate::ImageSequence::Animation {
591 frame_count: Some(200),
592 loop_count: None,
593 random_access: false,
594 },
595 );
596 let err = limits.check_image_info(&many_frames).unwrap_err();
597 assert_eq!(
598 err,
599 LimitExceeded::Frames {
600 actual: 200,
601 max: 100
602 }
603 );
604 }
605
606 #[test]
607 fn limit_exceeded_display() {
608 use alloc::format;
609 let err = LimitExceeded::Width {
610 actual: 5000,
611 max: 4096,
612 };
613 assert_eq!(format!("{err}"), "width 5000 exceeds limit 4096");
614
615 let err = LimitExceeded::InputSize {
616 actual: 20_000_000,
617 max: 10_000_000,
618 };
619 assert_eq!(
620 format!("{err}"),
621 "input size 20000000 bytes exceeds limit 10000000"
622 );
623
624 let err = LimitExceeded::Duration {
625 actual: 60_000,
626 max: 30_000,
627 };
628 assert_eq!(format!("{err}"), "duration 60000ms exceeds limit 30000ms");
629 }
630
631 #[test]
632 fn limit_exceeded_is_error() {
633 fn assert_error<E: core::error::Error>(_: &E) {}
634 let err = LimitExceeded::Width {
635 actual: 5000,
636 max: 4096,
637 };
638 assert_error(&err);
639 }
640}