pub struct Limits { /* private fields */ }Expand description
Operational limits for work performed on untrusted Contracts, sources, and values.
Limits never participate in Contract identity. Hosts may raise them explicitly for trusted workloads.
§Construction
Fields are private so adding a limit is never a breaking change.
Start from a profile and override individual fields with the
with_* builders:
use candid_core::{Limits, LimitsProfile};
let limits = LimitsProfile::InteractiveV1
.limits()
.with_max_input_bytes(64 * 1024)
.with_deadline_unix_ms(Some(2_000_000_000_000));
assert_eq!(limits.max_input_bytes(), 64 * 1024);Limits::default is LimitsProfile::InteractiveV1.
§Zero values
Every limit accepts 0; a zero limit is a defined, fail-closed
policy rather than a rejected configuration. A zero byte/count/work
limit rejects any input that consumes the resource at all, and
with_max_diagnostics(0) retains exactly one out-of-band
resource_limit_exceeded sentinel violation so an invalid input
never yields an empty error collection. deadline_unix_ms values
at or before the current time (including Some(0)) make every
bounded operation fail closed with operation_deadline_exceeded.
§Serialization
Limits serializes as the versioned portable configuration
described by LimitsConfig, never as a bare field map.
Implementations§
Source§impl Limits
impl Limits
Sourcepub fn max_input_bytes(&self) -> usize
pub fn max_input_bytes(&self) -> usize
Maximum bytes accepted by a bounded parse entry point before the document is decoded.
Sourcepub fn max_source_bytes(&self) -> usize
pub fn max_source_bytes(&self) -> usize
Maximum bytes of a single resolved DID source.
Sourcepub fn max_bundle_bytes(&self) -> usize
pub fn max_bundle_bytes(&self) -> usize
Maximum aggregate bytes across every source in a resolved bundle.
Sourcepub fn max_sources(&self) -> usize
pub fn max_sources(&self) -> usize
Maximum number of sources in a resolved bundle.
Sourcepub fn max_source_id_bytes(&self) -> usize
pub fn max_source_id_bytes(&self) -> usize
Maximum bytes in a single logical source ID (name/path).
Source IDs are otherwise bounded only cumulatively by
Limits::max_string_bytes, so one entry could carry a megabyte-long
path. This bounds each ID individually on both the resolver and the
embedded-sidecar paths.
Sourcepub fn max_import_depth(&self) -> usize
pub fn max_import_depth(&self) -> usize
Maximum import chain depth during source resolution.
Sourcepub fn max_import_edges(&self) -> usize
pub fn max_import_edges(&self) -> usize
Maximum import edges across a resolved bundle.
Sourcepub fn max_source_nesting(&self) -> usize
pub fn max_source_nesting(&self) -> usize
Maximum lexical nesting accepted before invoking the upstream parser.
Sourcepub fn max_type_depth(&self) -> usize
pub fn max_type_depth(&self) -> usize
Maximum semantic type nesting lowered from a checked Candid program.
Sourcepub fn max_value_nesting(&self) -> usize
pub fn max_value_nesting(&self) -> usize
Maximum lexical JSON container nesting accepted before invoking the
recursive serde_json decoder on a HostValue document.
This is the HostValue analogue of Limits::max_source_nesting: it
bounds lexical nesting ({ and [ in the JSON text) before a
recursive decoder runs, whereas Limits::max_value_depth bounds
semantic HostValue nesting after decoding. The two units differ — one
vec level costs two JSON containers and one record level costs
three — so a document rejected here reports value_nesting, never
value_depth.
Raising this above 128 has no effect: serde_json applies a fixed
128-frame recursion ceiling that this crate deliberately does not
disable, so documents nested deeper than 128 containers are rejected by
the decoder as malformed rather than by this limit.
§Choosing a value for a small stack
Rejecting a document costs constant stack, so no input can drive an
abort by being deeper than this limit. Accepting one still recurses,
at a cost that is build-profile dependent, so this limit is the knob for
matching decode to the stack the host actually runs on. Measured on a
64 KiB stack with a nested-opt document:
| Profile | Cost per container | Deepest safe |
|---|---|---|
| release | ~640 B | ~103 |
| debug | ~8 KiB | ~7 |
The default of 64 is chosen to keep a release build inside a 64 KiB
stack with roughly a third of it to spare, which is the bar
tests/deep_nesting.rs sets. A debug build on a stack that small needs
this lowered to single digits; a host on an ordinary 8 MiB stack can
raise it to 128 without approaching either bound.
Sourcepub fn max_type_nodes(&self) -> usize
pub fn max_type_nodes(&self) -> usize
Maximum type nodes in a Contract arena.
Sourcepub fn max_graph_edges(&self) -> usize
pub fn max_graph_edges(&self) -> usize
Maximum edges in a Contract type graph.
Sourcepub fn max_declarations(&self) -> usize
pub fn max_declarations(&self) -> usize
Maximum named declarations in a Contract.
Sourcepub fn max_fields(&self) -> usize
pub fn max_fields(&self) -> usize
Maximum aggregate record/variant fields across a Contract.
This bounds a Contract. It is not the ceiling on how wide a record
validate_host_value will accept: that is governed by
Limits::max_canonicalization_work, which at its default binds a
single record at 2 581 fields — far below the 500 000 permitted here.
Sourcepub fn max_methods(&self) -> usize
pub fn max_methods(&self) -> usize
Maximum aggregate service methods across a Contract.
Sourcepub fn max_function_values(&self) -> usize
pub fn max_function_values(&self) -> usize
Maximum aggregate function arguments and results across a Contract.
Sourcepub fn max_string_bytes(&self) -> usize
pub fn max_string_bytes(&self) -> usize
Maximum aggregate string bytes across declaration and method names.
Sourcepub fn max_producer_bytes(&self) -> usize
pub fn max_producer_bytes(&self) -> usize
Maximum aggregate bytes across the four producer metadata strings.
Producer metadata is untrusted, caller-supplied provenance that is
deliberately kept out of the semantic Contract identities (see
crate::ProducerInfo); this bounds the bytes it may contribute to a
validated Contract without ever affecting a semantic identity hash.
Sourcepub fn max_diagnostics(&self) -> usize
pub fn max_diagnostics(&self) -> usize
Maximum retained diagnostics per failure.
When more violations are observed than fit under this cap, the final
retained item is replaced by a resource_limit_exceeded sentinel
carrying the true observed count. A cap of 0 retains exactly that
one sentinel, so an invalid input never yields an empty error
collection.
Sourcepub fn max_canonicalization_work(&self) -> usize
pub fn max_canonicalization_work(&self) -> usize
Maximum canonicalization work units per operation.
This counter, not Limits::max_fields or
Limits::max_value_elements, is what bounds how wide a record
validate_host_value can accept. Record validation is
deliberately allocation-free: instead of building a field-ID index it
scans pairwise and charges one unit per comparison, checking the
deadline before each charge. Three such scans run per record — duplicate
detection, field-set agreement, and per-field lookup — so the cost is
roughly 1.5n² units for an n-field record.
At this default that puts the ceiling at 2 581 fields: a record with
2 582 fields fails closed with resource_limit_exceeded naming
canonicalization_work. That is three orders of magnitude below the
500 000 fields Limits::max_fields permits and the 1 000 000 elements
Limits::max_value_elements permits, so those two are not the binding
limit for wide records. Raise this counter to validate wider ones, and
note the cost grows quadratically. The failure is structured and
interruptible, never a hang.
The counter is per operation and shared with Contract canonicalization, so several moderately wide records in one value tree accumulate against the same budget.
Sourcepub fn max_provenance_work(&self) -> usize
pub fn max_provenance_work(&self) -> usize
Maximum work units charged while resolving provenance targets.
Kept separate from Limits::max_canonicalization_work so that
rederiving a large graph and then indexing its provenance sidecar cannot
jointly exhaust one counter. Bounds building each referenced container’s
field-ID / method-name index and every membership test, so adversarial
fan-out and duplicate provenance entries cannot drive an unbounded scan.
Sourcepub fn max_source_identity_work(&self) -> usize
pub fn max_source_identity_work(&self) -> usize
Maximum work units charged while serializing and hashing source-bundle
identity (candid-core:source-bundle:v1).
Each identity computation charges one unit per serialized payload byte
during an allocation-free counting pass, then reserves two more units
per byte (materializing and hashing the canonical bytes) plus the
domain-tag overhead before any allocation occurs. A presented sidecar
validation performs two passes on one budget — verifying the presented
source_bundle_id and emitting the rederived bundle’s ID — while a
plain compilation performs one.
Kept separate from Limits::max_canonicalization_work because the
serialized bundle scales with max_bundle_bytes: metering it on the
canonicalization counter would either starve graph work or force that
default far above what graph canonicalization needs. The default
accepts every bundle valid under the default byte/count limits: JSON
string escaping expands a byte to at most six, so one pass costs at
most 3 * 6 * (max_bundle_bytes + identity strings) + entry overhead,
about 213M units for a compile pass and 341M for the two validation
passes together; 400M covers both with headroom.
Sourcepub fn max_artifact_identity_work(&self) -> usize
pub fn max_artifact_identity_work(&self) -> usize
Maximum work units charged while computing a detached artifact identity
(candid-core:artifact:*; see crate::artifact_id_with_limits).
One unit per artifact byte plus the fixed domain framing cost — the
domain tag and its one separator byte. There is no length field and no
second kind label in the preimage, so that constant is the whole
overhead, and the cost is exactly
bytes.len() + domain.len() + 1.
The default is proven against the default byte gate rather than guessed.
Limits::max_input_bytes is enforced first, so the largest artifact
this ever hashes by default is 4 MiB (4 194 304 bytes); the longest of
the frozen domains,
candid-core:artifact:contract-envelope-json:v1, is 46 bytes, so the
worst case is 4 194 351 units. 10 000 000 covers that with more than
twice the headroom, matches
Limits::max_canonicalization_work’s default, and cannot overflow:
every charge saturates rather than wrapping.
Kept separate from every other work counter on purpose. Artifact
identity is an explicit, detached call, so metering it on
canonicalization_work would let content-addressing a document starve
the Contract canonicalization that follows it on the same budget, and
metering it on source_identity_work would do the same to provenance.
Raising Limits::max_input_bytes above this value without raising
this one makes over-sized artifacts fail on artifact_identity_work
instead of on input_bytes; raise both together.
§Wire compatibility
This override key is additive. Limits::default and every
configuration that leaves this limit at its profile value still
serialize with no max_artifact_identity_work key at all, so existing
documents are unchanged in both directions. The implication is
one-directional and deliberate: because LimitsConfig rejects unknown
override keys, a document that does carry an explicit
max_artifact_identity_work override is readable by this build and
newer ones but rejected by a build that predates the key. That is the
intended failure — silently ignoring an unknown limit override would
apply a policy the document did not ask for. LIMITS_CONFIG_VERSION
is unchanged, because adding a limit is not a schema break: Limits
fields are private precisely so that adding one is never a breaking
change, and no existing key, value, or default moved.
Sourcepub fn max_type_preflight_work(&self) -> usize
pub fn max_type_preflight_work(&self) -> usize
Maximum work units charged while enforcing Limits::max_type_depth.
Two iterative traversals guard type depth before anything recursive expands a parsed bundle: the parse-side preflight that follows declaration references before the upstream checker runs, and the checked-type walk that re-verifies the checker’s environment before lowering. Both walks — and the recursion map each builds first (the declaration reference graph and its cycle members) — charge this counter: one unit per visited syntax node or expansion state, plus one unit per recursive name tracked on the state’s path, which is the real cost of cloning and comparing that path set.
Shared subtrees are deduplicated: each node is expanded once per
distinct (node, depth, active recursive names) state rather than
once per referencing path, and only names that participate in a
reference cycle are ever tracked per path, so ordinary recursive
types add a small constant per state. The record DAG that motivated
this counter (issue #125: shared aliases re-expanded once per
incoming edge, O(2^n) visits from a 932-byte source) costs 2 796
units at n = 24 under deduplication — measured, and pinned together
with a minimal program’s exact cost by tests/deep_nesting.rs and
the unit tests beside the walks. Shapes that still multiply states —
deep stacks of distinct-name diamonds, enormous mutual-recursion
groups, long structural alias chains re-walked from every
declaration root at shifted depths — exhaust this counter and fail
closed with resource_limit_exceeded naming type_preflight_work,
instead of hanging.
Kept separate from Limits::max_canonicalization_work because both
counters accrue on one budget in a single compilation: metering the
depth guard on the graph counter would let a type-heavy bundle starve
the canonicalization that follows it on the same budget. The default
matches that counter’s and covers realistic contracts by several
orders of magnitude: the largest corpus fixture (ledger.did)
consumes 806 units end to end, pinned by tests/deep_nesting.rs.
§Choosing a value for a small heap
Deduplication is what makes these walks cheap, and it is also what
makes them retain: each distinct state stays in a memo for the
duration of the walk, where the previous tree walks held only a stack.
So this counter bounds memory as well as time, and the exchange rate
is measured rather than assumed: about 19 bytes of peak live heap
per unit, near-constant across bundle sizes and pinned by
tests/type_preflight_memory.rs.
At this default that authorizes roughly 190 MB of peak heap before
the counter refuses — ample headroom on an ordinary host, and more
than a small 32-bit wasm32 heap can serve. A host whose allocator
fails before the counter does gets an allocation abort in place of
the clean, structured resource_limit_exceeded this is designed to
return, so a browser or other constrained host should lower this to
what its heap can actually hold: 1 000 000 keeps the walks under
~19 MB and still clears every realistic contract by three orders of
magnitude. Rejecting costs no memory, so no input can drive an abort
by being larger than the configured bound — only by being accepted
under a bound the host cannot honor.
The rate holds because a state’s cost does not depend on anything an attacker picks freely: recursive names are interned to dense indices rather than compared or stored as text, and each path’s set is shared with the children that inherit it unchanged rather than copied into every one of a wide record’s fields.
§Wire compatibility
This override key is additive, exactly like
Limits::max_artifact_identity_work’s: Limits::default and
every configuration that leaves this limit at its profile value still
serialize with no max_type_preflight_work key at all, so existing
documents are unchanged in both directions, while a document that does
carry the key is readable by this build and newer ones but rejected by
a build that predates it — the intended failure, since silently
ignoring an unknown limit override would apply a policy the document
did not ask for. LIMITS_CONFIG_VERSION is unchanged: Limits
fields are private precisely so that adding one is never a breaking
change, and no existing key, value, or default moved.
Sourcepub fn max_value_depth(&self) -> usize
pub fn max_value_depth(&self) -> usize
Maximum semantic HostValue nesting depth.
Sourcepub fn max_value_elements(&self) -> usize
pub fn max_value_elements(&self) -> usize
Maximum aggregate HostValue elements per document.
Reaching this ceiling with a single wide record is not possible at
default limits: Limits::max_canonicalization_work binds one record
at 2 581 fields, well before the million elements permitted here. This
limit is the binding one for wide vectors and for element counts
accumulated across a value tree.
Sourcepub fn max_value_bytes(&self) -> usize
pub fn max_value_bytes(&self) -> usize
Maximum aggregate HostValue text/blob bytes per document.
Sourcepub fn with_max_input_bytes(self, value: usize) -> Self
pub fn with_max_input_bytes(self, value: usize) -> Self
Returns self with max_input_bytes replaced. See Limits::max_input_bytes.
Sourcepub fn with_max_source_bytes(self, value: usize) -> Self
pub fn with_max_source_bytes(self, value: usize) -> Self
Returns self with max_source_bytes replaced. See Limits::max_source_bytes.
Sourcepub fn with_max_bundle_bytes(self, value: usize) -> Self
pub fn with_max_bundle_bytes(self, value: usize) -> Self
Returns self with max_bundle_bytes replaced. See Limits::max_bundle_bytes.
Sourcepub fn with_max_sources(self, value: usize) -> Self
pub fn with_max_sources(self, value: usize) -> Self
Returns self with max_sources replaced. See Limits::max_sources.
Sourcepub fn with_max_source_id_bytes(self, value: usize) -> Self
pub fn with_max_source_id_bytes(self, value: usize) -> Self
Returns self with max_source_id_bytes replaced. See Limits::max_source_id_bytes.
Sourcepub fn with_max_import_depth(self, value: usize) -> Self
pub fn with_max_import_depth(self, value: usize) -> Self
Returns self with max_import_depth replaced. See Limits::max_import_depth.
Sourcepub fn with_max_import_edges(self, value: usize) -> Self
pub fn with_max_import_edges(self, value: usize) -> Self
Returns self with max_import_edges replaced. See Limits::max_import_edges.
Sourcepub fn with_max_source_nesting(self, value: usize) -> Self
pub fn with_max_source_nesting(self, value: usize) -> Self
Returns self with max_source_nesting replaced. See Limits::max_source_nesting.
Sourcepub fn with_max_type_depth(self, value: usize) -> Self
pub fn with_max_type_depth(self, value: usize) -> Self
Returns self with max_type_depth replaced. See Limits::max_type_depth.
Sourcepub fn with_max_value_nesting(self, value: usize) -> Self
pub fn with_max_value_nesting(self, value: usize) -> Self
Returns self with max_value_nesting replaced. See Limits::max_value_nesting.
Sourcepub fn with_max_type_nodes(self, value: usize) -> Self
pub fn with_max_type_nodes(self, value: usize) -> Self
Returns self with max_type_nodes replaced. See Limits::max_type_nodes.
Sourcepub fn with_max_graph_edges(self, value: usize) -> Self
pub fn with_max_graph_edges(self, value: usize) -> Self
Returns self with max_graph_edges replaced. See Limits::max_graph_edges.
Sourcepub fn with_max_declarations(self, value: usize) -> Self
pub fn with_max_declarations(self, value: usize) -> Self
Returns self with max_declarations replaced. See Limits::max_declarations.
Sourcepub fn with_max_fields(self, value: usize) -> Self
pub fn with_max_fields(self, value: usize) -> Self
Returns self with max_fields replaced. See Limits::max_fields.
Sourcepub fn with_max_methods(self, value: usize) -> Self
pub fn with_max_methods(self, value: usize) -> Self
Returns self with max_methods replaced. See Limits::max_methods.
Sourcepub fn with_max_function_values(self, value: usize) -> Self
pub fn with_max_function_values(self, value: usize) -> Self
Returns self with max_function_values replaced. See Limits::max_function_values.
Sourcepub fn with_max_string_bytes(self, value: usize) -> Self
pub fn with_max_string_bytes(self, value: usize) -> Self
Returns self with max_string_bytes replaced. See Limits::max_string_bytes.
Sourcepub fn with_max_producer_bytes(self, value: usize) -> Self
pub fn with_max_producer_bytes(self, value: usize) -> Self
Returns self with max_producer_bytes replaced. See Limits::max_producer_bytes.
Sourcepub fn with_max_diagnostics(self, value: usize) -> Self
pub fn with_max_diagnostics(self, value: usize) -> Self
Returns self with max_diagnostics replaced. See Limits::max_diagnostics.
Sourcepub fn with_max_canonicalization_work(self, value: usize) -> Self
pub fn with_max_canonicalization_work(self, value: usize) -> Self
Returns self with max_canonicalization_work replaced. See Limits::max_canonicalization_work.
Sourcepub fn with_max_provenance_work(self, value: usize) -> Self
pub fn with_max_provenance_work(self, value: usize) -> Self
Returns self with max_provenance_work replaced. See Limits::max_provenance_work.
Sourcepub fn with_max_source_identity_work(self, value: usize) -> Self
pub fn with_max_source_identity_work(self, value: usize) -> Self
Returns self with max_source_identity_work replaced. See Limits::max_source_identity_work.
Sourcepub fn with_max_artifact_identity_work(self, value: usize) -> Self
pub fn with_max_artifact_identity_work(self, value: usize) -> Self
Returns self with max_artifact_identity_work replaced. See Limits::max_artifact_identity_work.
Sourcepub fn with_max_type_preflight_work(self, value: usize) -> Self
pub fn with_max_type_preflight_work(self, value: usize) -> Self
Returns self with max_type_preflight_work replaced. See Limits::max_type_preflight_work.
Sourcepub fn with_max_value_depth(self, value: usize) -> Self
pub fn with_max_value_depth(self, value: usize) -> Self
Returns self with max_value_depth replaced. See Limits::max_value_depth.
Sourcepub fn with_max_value_elements(self, value: usize) -> Self
pub fn with_max_value_elements(self, value: usize) -> Self
Returns self with max_value_elements replaced. See Limits::max_value_elements.
Sourcepub fn with_max_value_bytes(self, value: usize) -> Self
pub fn with_max_value_bytes(self, value: usize) -> Self
Returns self with max_value_bytes replaced. See Limits::max_value_bytes.
Source§impl Limits
impl Limits
Sourcepub fn profile(&self) -> LimitsProfile
pub fn profile(&self) -> LimitsProfile
The profile these limits started from. Overridden fields do not change the profile; it names the baseline, not the final values.
Sourcepub fn deadline_unix_ms(&self) -> Option<u64>
pub fn deadline_unix_ms(&self) -> Option<u64>
Optional Unix timestamp in milliseconds after which work must abort.
None means no deadline. A value at or before the current time makes
every bounded operation fail closed with
operation_deadline_exceeded before performing work.
Bare wasm32-unknown-unknown has no clock, so a deadline cannot be
measured there: any explicit deadline fails closed on that target,
while None stays unbounded. Cancellation and every quantitative limit
are unaffected. See Limits::deadline_exceeded.
Sourcepub fn with_deadline_unix_ms(self, deadline_unix_ms: Option<u64>) -> Self
pub fn with_deadline_unix_ms(self, deadline_unix_ms: Option<u64>) -> Self
Returns self with deadline_unix_ms replaced. See
Limits::deadline_unix_ms.
Sourcepub fn deadline_exceeded(&self) -> bool
pub fn deadline_exceeded(&self) -> bool
Whether the configured deadline has already elapsed.
No deadline is never exceeded. On bare wasm32-unknown-unknown there
is no clock to compare against — SystemTime::now panics rather than
failing — so an explicit deadline is reported as elapsed instead of
aborting the process. That is the same fail-closed direction the
internal budget takes, and it keeps an explicit deadline from silently
becoming unbounded on the one target that cannot honour it.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Limits
impl<'de> Deserialize<'de> for Limits
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Limits
Source§impl From<&Limits> for LimitsConfig
impl From<&Limits> for LimitsConfig
Source§impl From<Limits> for LimitsConfig
impl From<Limits> for LimitsConfig
impl StructuralPartialEq for Limits
Source§impl TryFrom<LimitsConfig> for Limits
impl TryFrom<LimitsConfig> for Limits
Source§type Error = LimitsConfigError
type Error = LimitsConfigError
Source§fn try_from(config: LimitsConfig) -> Result<Self, LimitsConfigError>
fn try_from(config: LimitsConfig) -> Result<Self, LimitsConfigError>
Auto Trait Implementations§
impl Freeze for Limits
impl RefUnwindSafe for Limits
impl Send for Limits
impl Sync for Limits
impl Unpin for Limits
impl UnsafeUnpin for Limits
impl UnwindSafe for Limits
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.