ic-platform 0.6.4

Controlled Candid and IC runtime dependency boundary
Documentation
use super::deserialize;
use serde::de::{
    DeserializeSeed, SeqAccess,
    value::{Error, SeqAccessDeserializer, U64Deserializer},
};
use std::cell::Cell;

struct Sequence<'a> {
    values: &'a [u64],
    hint: Option<usize>,
    reads: &'a Cell<usize>,
}

impl<'de> SeqAccess<'de> for Sequence<'_> {
    type Error = Error;

    fn next_element_seed<T: DeserializeSeed<'de>>(
        &mut self,
        seed: T,
    ) -> Result<Option<T::Value>, Error> {
        let Some((first, rest)) = self.values.split_first() else {
            return Ok(None);
        };
        self.values = rest;
        self.reads.set(self.reads.get() + 1);
        seed.deserialize(U64Deserializer::new(*first)).map(Some)
    }

    fn size_hint(&self) -> Option<usize> {
        self.hint
    }
}

fn read<const LIMIT: usize>(
    values: &[u64],
    hint: Option<usize>,
    reads: &Cell<usize>,
) -> Result<Vec<u8>, Error> {
    deserialize::<_, u8, LIMIT>(
        SeqAccessDeserializer::new(Sequence {
            values,
            hint,
            reads,
        }),
        "bounded bytes",
        "sequence limit",
    )
}

#[test]
fn exact_and_zero_bounds_preserve_values() {
    let reads = Cell::new(0);
    assert_eq!(read::<2>(&[0, 255], Some(2), &reads).unwrap(), [0, 255]);
    assert_eq!(reads.get(), 2);
    assert_eq!(read::<0>(&[], None, &reads).unwrap(), [] as [u8; 0]);
    assert_eq!(
        read::<0>(&[1], None, &reads).unwrap_err().to_string(),
        "sequence limit"
    );
}

#[test]
fn excessive_hints_reject_before_reading_or_allocating_from_them() {
    let reads = Cell::new(0);
    assert_eq!(
        read::<2>(&[1], Some(usize::MAX), &reads)
            .unwrap_err()
            .to_string(),
        "sequence limit"
    );
    assert_eq!(reads.get(), 0);
}

#[test]
fn absent_or_deceptive_hints_read_only_one_typed_excess_element() {
    for hint in [None, Some(0), Some(2)] {
        let reads = Cell::new(0);
        assert_eq!(
            read::<2>(&[1, 2, 3, 4], hint, &reads)
                .unwrap_err()
                .to_string(),
            "sequence limit"
        );
        assert_eq!(reads.get(), 3);
        reads.set(0);
        let error = read::<2>(&[1, 2, u64::MAX, 4], hint, &reads).unwrap_err();
        assert!(error.to_string().contains("invalid value"));
        assert_eq!(reads.get(), 3);
    }
}

#[test]
fn candid_sequences_keep_explicit_work_and_zero_skip_limits() {
    use candid::CandidType;
    use serde::{Deserialize, Deserializer};
    fn bytes<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
        deserialize::<_, _, 2>(d, "bounded bytes", "sequence limit")
    }
    #[derive(CandidType, Deserialize)]
    struct Bounded {
        #[serde(deserialize_with = "bytes")]
        bytes: Vec<u8>,
    }
    let limits = crate::candid::DecodeLimits {
        reply_bytes: 1024,
        decoding_quota: 10_000,
        skipping_quota: 0,
        type_entries: 4,
        header_bytes: 128,
    };
    let wire = candid::encode_one(Bounded {
        bytes: vec![0, 255],
    })
    .unwrap();
    assert_eq!(
        crate::candid::decode_exactly_one::<Bounded>(&wire, limits)
            .unwrap()
            .bytes,
        [0, 255]
    );
    let wire = candid::encode_one(Bounded {
        bytes: vec![0, 1, 2],
    })
    .unwrap();
    assert!(crate::candid::decode_exactly_one::<Bounded>(&wire, limits).is_err());
}