ic-query 0.101.0

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
//! Bounded external Candid encoding and reply decoding shared by host and canister adapters.

#[cfg(any(test, feature = "cmc-host"))]
use ic_platform::candid_codec::{Encode, encode_one};
use ic_platform::candid_codec::{
    Error as EncodeError,
    arguments::{EncodeArgs, encode_args},
    decoder::{Arity, Decode, Error as PlatformDecodeError, Limits, decode_args},
};

const MAX_ENCODED_BYTES: usize = 8 * 1024 * 1024;
const BASE_WORK: usize = 64 * 1024;
const MAX_WORK: usize = 256_000_000;
const BASE_ALLOCATION: usize = 1024 * 1024;
const MAX_ALLOCATION: usize = 256 * 1024 * 1024;
const MAX_TYPE_ENTRIES: usize = 4_096;
const MAX_HEADER_BYTES: usize = 64 * 1024;

///
/// CandidReplyError
///
/// Query-owned projection of malformed replies and bounded codec refusals.
///

#[derive(Debug, thiserror::Error)]
pub enum CandidReplyError {
    #[error("Candid reply decoding quota exceeded")]
    Budget,
    #[error("invalid Candid reply: {reason:?}")]
    Protocol { reason: PlatformDecodeError },
}

pub fn encode_arguments<T: EncodeArgs>(args: &T) -> Result<Vec<u8>, EncodeError> {
    encode_args(args, MAX_ENCODED_BYTES)
}

#[cfg(any(test, feature = "cmc-host"))]
pub fn encode_value<T: Encode + ?Sized>(value: &T) -> Result<Vec<u8>, EncodeError> {
    encode_one(value, MAX_ENCODED_BYTES)
}

pub fn decode_reply<'a, T: Decode<'a>>(bytes: &'a [u8]) -> Result<T, CandidReplyError> {
    // Transport owners admit wire size. These codec-specific bounds cover work,
    // logical allocation and validated unused arguments, including tiny-message amplification.
    let limits = Limits {
        bytes: bytes.len(),
        header: MAX_HEADER_BYTES,
        types: MAX_TYPE_ENTRIES,
        work: bytes
            .len()
            .saturating_mul(32)
            .saturating_add(BASE_WORK)
            .min(MAX_WORK),
        allocation: bytes
            .len()
            .saturating_mul(16)
            .saturating_add(BASE_ALLOCATION)
            .min(MAX_ALLOCATION),
    };
    decode_args::<(T,)>(bytes, limits, Arity::Compatible)
        .map(|(value,)| value)
        .map_err(|reason| match reason {
            PlatformDecodeError::Limit => CandidReplyError::Budget,
            reason => CandidReplyError::Protocol { reason },
        })
}

#[cfg(test)]
mod tests;