Untrusted input

Limits and their defaults, Scene::check and Stats, the bounds inside the importers and the Lottie expression budget, renderer bounds, and when a file is an error or a warning.

An animation downloaded at run time is a document from the network. Reading one is bounded by Limits: a file over a limit is refused with an error naming it, before it can take unbounded memory or time. Pass tighter limits than the defaults when the files are not your own:

use dxn_vector_animation::{Error, Import, Limits};

/// Limits for animations downloaded at run time: small files, short timelines.
const REMOTE: Limits = Limits {
    max_bytes: 2 * 1024 * 1024,
    max_nodes: 5_000,
    max_keyframes: 50_000,
    max_depth: 32,
    max_path_points: 200_000,
    max_duration: 60.0,
};

fn read_remote(data: &[u8]) -> Result<Import, Error> {
    if dxn_vector_animation_lottie::sniff(data) {
        dxn_vector_animation_lottie::read_with(data, &REMOTE)
    } else {
        dxn_vector_animation_svg::read_with(data, &REMOTE)
    }
}

let long = br#"<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
  <rect width="10" height="10"><animate attributeName="x" values="0;5" dur="90s"/></rect>
</svg>"#;
match read_remote(long) {
    Err(Error::Limit { what, limit }) => println!("refused: over {limit} {what}"),
    Err(e) => println!("refused: {e}"),
    Ok(import) => println!("read, {} warnings", import.warnings.len()),
}
refused: over 60 seconds of timeline

#Limits

Limits is a Copy struct of public fields; Limits::default() holds the defaults. The same limits apply to SVG, Lottie, dotLottie and compiled scenes.

FieldDefaultBounds
max_bytes32 MiB (33,554,432)The file’s size; a dotLottie archive’s unpacked bytes too
max_nodes100,000Groups, shapes (text included) and images; an SVG’s XML nodes at 8 times it
max_keyframes1,000,000Keyframes of every track together
max_depth64Groups nested in groups, masks, patterns and nested compositions included
max_path_points4,000,000Points of every path and every keyframe of a path together, motion paths included
max_duration3600.0Seconds of timeline

The error is Error::Limit { what, limit }, displayed as the file exceeds the limit of {limit} {what}. what is one of bytes, bytes unpacked, archive entries, nodes, keyframes, levels of nesting, path points, seconds of timeline, text animator samples and text keyframes.

#Checking a scene

The importers count while they read and check the finished scene once more. Scene::check(&limits) does that check on any scene, such as one deserialized from your own format, and returns its Stats:

use dxn_vector_animation::Limits;

let scene = dxn_vector_animation_lottie::read(&std::fs::read("fixtures/lottie-motion.json")?)?.scene;
let stats = scene.check(&Limits::default())?;
println!("{stats:?}");
Stats { groups: 6, shapes: 3, images: 0, animated_properties: 3, keyframes: 6, depth: 4, path_points: 0 }

Scene::stats() counts without checking. vector-animation inspect prints the same counts.

#Bounds inside the importers

Some work grows with what a file asks for rather than with its size: iterations to unroll, copies to make, outlines to bake. The importers cap it:

WhereBoundPast it
SVG: SMIL4,000 animation instances resolved on the timeline, and 4,000 more for what begins on events (a cycle of begins cannot use up what a click needs)Later instances are left out, with a warning
SVG: CSS and SMIL10,000 iterations of one animation unrolledLater iterations are left out, with a warning
SVG: animations repeating foreverOne loop for all of them within max_duration and 10,000 iterations of eachThe timeline is as long as the longest and loops from 0, with a warning
Lottie: repeaters1,000 copies; 10,000 steps of the transform for one copy1,000 copies are kept, and copies further away stop at 10,000 steps, each with a warning
Lottie: shape modifiersBaked outlines within half of max_path_points and of max_keyframes; 20,000 sampled moments and 200,000 points for one outlineFewer moments or zig zag ridges, or the modifier left out, with a warning
Lottie: merge paths20,000 points of flattened outlines and 1,000,000 overlapping segment pairs for one mergeThe outlines stay unmerged, with a warning
Lottie: text animators2,000,000 letters × animators for one text document; 4,000,000 letters × animators × sampled times; all text keyframes within half of max_keyframesError::Limit (the file exceeds the limit of 2000000 text animator samples); sampled less often, with a warning; Error::Limit (the file exceeds the limit of 500000 text keyframes, half of max_keyframes) when even two samples are too many
dotLottie10,000 archive entriesError::Limit (archive entries)

#Lottie expressions

Expressions are programs. The interpreter runs each within a budget, and an expression over any bound is left out: its property keeps its keyframes, with a warning such as expressions over the evaluation budget are left out: their properties keep their keyframes.

BoundValue
Steps for one property, over all its frames4,000,000
Steps for all expressions of a file40,000,000
Recursion of statements, expressions and calls256 levels
Nested function calls32
Array length65,536 elements
String length65,536 bytes
Arrays nested in arrays16
Expression source256 KiB
Nesting while parsing / depth of one expression’s tree128 / 160
Expressions reading one another in a chain16
Keyframes written by all expressionsHalf of max_keyframes

Random numbers and wiggles are seeded by their property, so every import of a file gives the same scene.

#Sampling and rendering

  • Scene::frame_at follows nested groups 256 levels deep at most (the importers refuse far less), drops groups whose matrix is not finite, and caps blur and shadow deviations at 10,000 scene lengths.
  • The reference renderer refuses images of more than 8192 × 8192 pixels with RenderError::Size, and draws a pattern’s tile at 4096 pixels a side at most. Clamp a size that comes from a file before rendering it.
  • vector-animation render --fps renders at most 100,001 frames.

#Errors or warnings

OutcomeWhenWhat to do
Err(Error::Limit { .. })Over a limitRefuse the file, or raise the limit if the file is yours
Err(Error::Invalid(..))Not a valid documentRefuse the file
Err(Error::Unsupported(..))Nothing of it can be represented (.svgz, ZIP64)Convert the file
Ok(import) with warningsParts left out or approximatedShow them to whoever made the file; fail a build with vector-animation validate --strict

Next: The vector-animation CLI, or the inputs: SVG and Lottie.