1use crate::limits::Limits;
14use crate::model::{Contract, PrimitiveType, TypeNode, TypeRef};
15use serde::{Deserialize, Serialize};
16use std::fmt;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct ContractTypeRef {
21 pub contract_id: String,
22 pub type_ref: TypeRef,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct ContractMethodRef {
28 pub contract_id: String,
29 pub method_name: String,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
39pub struct HostFieldValue {
40 id: u32,
41 value: HostValue,
42}
43
44impl HostFieldValue {
45 pub fn new(id: u32, value: HostValue) -> Self {
51 Self { id, value }
52 }
53
54 pub fn id(&self) -> u32 {
55 self.id
56 }
57
58 pub fn value(&self) -> &HostValue {
59 &self.value
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct HostValue {
80 kind: HostValueKind,
81 extent: Extent,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94struct Extent {
95 depth: u32,
96 elements: u32,
97}
98
99impl Extent {
100 const LEAF: Self = Self {
101 depth: 0,
102 elements: 1,
103 };
104
105 fn of(kind: &HostValueKind) -> Self {
106 match kind {
107 HostValueKind::Opt { value: Some(value) } => Self::enclosing([value.extent]),
108 HostValueKind::Vec { values } => {
109 Self::enclosing(values.iter().map(|value| value.extent))
110 }
111 HostValueKind::Record { fields } => {
112 Self::enclosing(fields.iter().map(|field| field.value.extent))
113 }
114 HostValueKind::Variant { value, .. } => Self::enclosing([value.extent]),
115 _ => Self::LEAF,
116 }
117 }
118
119 fn enclosing(children: impl IntoIterator<Item = Self>) -> Self {
122 let mut deepest: Option<u32> = None;
123 let mut elements = 1u32;
124 for child in children {
125 deepest = Some(deepest.map_or(child.depth, |depth| depth.max(child.depth)));
126 elements = elements.saturating_add(child.elements);
127 }
128 Self {
129 depth: deepest.map_or(0, |depth| depth.saturating_add(1)),
130 elements,
131 }
132 }
133}
134
135impl Serialize for HostValue {
139 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140 where
141 S: serde::Serializer,
142 {
143 self.kind.serialize(serializer)
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
148#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
149enum HostValueKind {
150 Null,
151 Bool { value: bool },
152 Nat { value: String },
153 Int { value: String },
154 Nat8 { value: u8 },
155 Nat16 { value: u16 },
156 Nat32 { value: u32 },
157 Nat64 { value: String },
158 Int8 { value: i8 },
159 Int16 { value: i16 },
160 Int32 { value: i32 },
161 Int64 { value: String },
162 Float32 { bits: String },
163 Float64 { bits: String },
164 Text { value: String },
165 Reserved,
166 Principal { value: String },
167 Opt { value: Option<Box<HostValue>> },
168 Vec { values: Vec<HostValue> },
169 Record { fields: Vec<HostFieldValue> },
170 Variant { id: u32, value: Box<HostValue> },
171 Service { principal: String },
172 Func { principal: String, method: String },
173}
174
175impl HostValue {
176 pub fn from_json_with_limits(input: &str, limits: &Limits) -> Result<Self, HostValueJsonError> {
177 Self::from_json_with_context(input, &crate::RuntimeContext::new(limits.clone()))
178 }
179
180 pub fn from_json_with_context(
181 input: &str,
182 context: &crate::RuntimeContext,
183 ) -> Result<Self, HostValueJsonError> {
184 let limits = &context.limits;
185 if input.len() > limits.max_value_bytes {
186 return Err(HostValueJsonError::Limit {
187 limit: limits.max_value_bytes,
188 observed: input.len(),
189 });
190 }
191 let mut budget = context.budget();
192 budget
193 .checkpoint()
194 .map_err(|error| host_json_budget_error(error, "$"))?;
195 check_value_nesting(input, &mut budget)?;
196 let raw: RawHostValue = serde_json::from_str(input)
197 .map_err(|error| HostValueJsonError::Malformed(error.to_string()))?;
198 budget
199 .checkpoint()
200 .map_err(|error| host_json_budget_error(error, "$"))?;
201 HostValueLocalValidationState::new(&mut budget).canonicalize(raw)
202 }
203
204 pub fn null() -> Self {
205 Self::scalar(HostValueKind::Null)
206 }
207
208 pub fn boolean(value: bool) -> Self {
209 Self::scalar(HostValueKind::Bool { value })
210 }
211
212 pub fn nat(value: impl Into<String>) -> Result<Self, HostValueJsonError> {
213 let value = value.into();
214 Self::require(canonical_nat(&value), "non-canonical nat")?;
215 Ok(Self::scalar(HostValueKind::Nat { value }))
216 }
217
218 pub fn int(value: impl Into<String>) -> Result<Self, HostValueJsonError> {
219 let value = value.into();
220 Self::require(canonical_int(&value), "non-canonical int")?;
221 Ok(Self::scalar(HostValueKind::Int { value }))
222 }
223
224 pub fn nat8(value: u8) -> Self {
225 Self::scalar(HostValueKind::Nat8 { value })
226 }
227
228 pub fn nat16(value: u16) -> Self {
229 Self::scalar(HostValueKind::Nat16 { value })
230 }
231
232 pub fn nat32(value: u32) -> Self {
233 Self::scalar(HostValueKind::Nat32 { value })
234 }
235
236 pub fn nat64(value: impl Into<String>) -> Result<Self, HostValueJsonError> {
237 let value = value.into();
238 Self::require(
239 canonical_nat(&value) && value.parse::<u64>().is_ok(),
240 "non-canonical nat64",
241 )?;
242 Ok(Self::scalar(HostValueKind::Nat64 { value }))
243 }
244
245 pub fn int8(value: i8) -> Self {
246 Self::scalar(HostValueKind::Int8 { value })
247 }
248
249 pub fn int16(value: i16) -> Self {
250 Self::scalar(HostValueKind::Int16 { value })
251 }
252
253 pub fn int32(value: i32) -> Self {
254 Self::scalar(HostValueKind::Int32 { value })
255 }
256
257 pub fn int64(value: impl Into<String>) -> Result<Self, HostValueJsonError> {
258 let value = value.into();
259 Self::require(
260 canonical_int(&value) && value.parse::<i64>().is_ok(),
261 "non-canonical int64",
262 )?;
263 Ok(Self::scalar(HostValueKind::Int64 { value }))
264 }
265
266 pub fn float32(bits: impl Into<String>) -> Result<Self, HostValueJsonError> {
267 let bits = bits.into();
268 Self::require(canonical_hex(&bits, 8), "non-canonical float32 bits")?;
269 Ok(Self::scalar(HostValueKind::Float32 { bits }))
270 }
271
272 pub fn float64(bits: impl Into<String>) -> Result<Self, HostValueJsonError> {
273 let bits = bits.into();
274 Self::require(canonical_hex(&bits, 16), "non-canonical float64 bits")?;
275 Ok(Self::scalar(HostValueKind::Float64 { bits }))
276 }
277
278 pub fn text(value: impl Into<String>) -> Self {
279 Self::scalar(HostValueKind::Text {
280 value: value.into(),
281 })
282 }
283
284 pub fn reserved() -> Self {
285 Self::scalar(HostValueKind::Reserved)
286 }
287
288 pub fn principal(value: impl Into<String>) -> Result<Self, HostValueJsonError> {
289 let value = value.into();
290 Self::require_canonical_principal(&value)?;
291 Ok(Self::scalar(HostValueKind::Principal { value }))
292 }
293
294 pub fn opt(value: Option<Self>, limits: &Limits) -> Result<Self, HostValueJsonError> {
302 Self::bounded(
303 HostValueKind::Opt {
304 value: value.map(Box::new),
305 },
306 limits,
307 )
308 }
309
310 pub fn vector(values: Vec<Self>, limits: &Limits) -> Result<Self, HostValueJsonError> {
313 Self::bounded(HostValueKind::Vec { values }, limits)
314 }
315
316 pub fn record(
323 fields: Vec<HostFieldValue>,
324 limits: &Limits,
325 ) -> Result<Self, HostValueJsonError> {
326 Self::bounded(HostValueKind::Record { fields }, limits)
327 }
328
329 pub fn variant(id: u32, value: Self, limits: &Limits) -> Result<Self, HostValueJsonError> {
332 Self::bounded(
333 HostValueKind::Variant {
334 id,
335 value: Box::new(value),
336 },
337 limits,
338 )
339 }
340
341 pub fn service(principal: impl Into<String>) -> Result<Self, HostValueJsonError> {
342 let principal = principal.into();
343 Self::require_canonical_principal(&principal)?;
344 Ok(Self::scalar(HostValueKind::Service { principal }))
345 }
346
347 pub fn func(
348 principal: impl Into<String>,
349 method: impl Into<String>,
350 ) -> Result<Self, HostValueJsonError> {
351 let principal = principal.into();
352 Self::require_canonical_principal(&principal)?;
353 Ok(Self::scalar(HostValueKind::Func {
354 principal,
355 method: method.into(),
356 }))
357 }
358
359 fn scalar(kind: HostValueKind) -> Self {
361 debug_assert!(
362 Extent::of(&kind) == Extent::LEAF,
363 "scalar() used for a kind that encloses children",
364 );
365 Self {
366 kind,
367 extent: Extent::LEAF,
368 }
369 }
370
371 fn measured(kind: HostValueKind) -> Self {
377 let extent = Extent::of(&kind);
378 Self { kind, extent }
379 }
380
381 fn bounded(kind: HostValueKind, limits: &Limits) -> Result<Self, HostValueJsonError> {
388 let extent = Extent::of(&kind);
389 if usize::try_from(extent.depth).unwrap_or(usize::MAX) > limits.max_value_depth {
390 return Err(HostValueJsonError::ValueLimit {
391 resource: "value_depth",
392 limit: limits.max_value_depth,
393 observed: usize::try_from(extent.depth).unwrap_or(usize::MAX),
394 path: "$".to_string(),
395 });
396 }
397 if usize::try_from(extent.elements).unwrap_or(usize::MAX) > limits.max_value_elements {
398 return Err(HostValueJsonError::ValueLimit {
399 resource: "value_elements",
400 limit: limits.max_value_elements,
401 observed: usize::try_from(extent.elements).unwrap_or(usize::MAX),
402 path: "$".to_string(),
403 });
404 }
405 Ok(Self { kind, extent })
406 }
407
408 fn require(condition: bool, message: &str) -> Result<(), HostValueJsonError> {
409 if condition {
410 Ok(())
411 } else {
412 Err(HostValueJsonError::Malformed(format!("$: {message}")))
413 }
414 }
415
416 fn require_canonical_principal(value: &str) -> Result<(), HostValueJsonError> {
417 let principal = ic_principal::Principal::from_text(value).map_err(|error| {
418 HostValueJsonError::Malformed(format!("$: invalid principal {value:?}: {error}"))
419 })?;
420 Self::require(principal.to_text() == value, "non-canonical principal")
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
425pub enum HostValueJsonError {
426 Malformed(String),
427 Limit {
428 limit: usize,
429 observed: usize,
430 },
431 ValueLimit {
432 resource: &'static str,
433 limit: usize,
434 observed: usize,
435 path: String,
436 },
437 Deadline {
438 path: String,
439 },
440 Cancelled {
441 path: String,
442 },
443}
444
445impl fmt::Display for HostValueJsonError {
446 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
447 match self {
448 Self::Malformed(message) => write!(formatter, "malformed HostValue JSON: {message}"),
449 Self::Limit { limit, observed } => write!(
450 formatter,
451 "HostValue JSON uses {observed} bytes; limit is {limit}"
452 ),
453 Self::ValueLimit {
454 resource,
455 limit,
456 observed,
457 path,
458 } => write!(
459 formatter,
460 "HostValue JSON at {path} uses {observed} {resource}; limit is {limit}"
461 ),
462 Self::Deadline { path } => {
463 write!(
464 formatter,
465 "HostValue JSON validation deadline elapsed at {path}"
466 )
467 }
468 Self::Cancelled { path } => {
469 write!(formatter, "HostValue JSON validation cancelled at {path}")
470 }
471 }
472 }
473}
474
475impl std::error::Error for HostValueJsonError {}
476
477fn host_json_budget_error(error: crate::budget::BudgetError, path: &str) -> HostValueJsonError {
478 match error {
479 crate::budget::BudgetError::Cancelled => HostValueJsonError::Cancelled {
480 path: path.to_string(),
481 },
482 crate::budget::BudgetError::DeadlineExceeded => HostValueJsonError::Deadline {
483 path: path.to_string(),
484 },
485 crate::budget::BudgetError::ResourceLimit {
486 resource,
487 limit,
488 observed,
489 } => HostValueJsonError::ValueLimit {
490 resource,
491 limit,
492 observed,
493 path: path.to_string(),
494 },
495 }
496}
497
498const NESTING_CHECKPOINT_INTERVAL: usize = 4096;
502
503fn check_value_nesting(
522 input: &str,
523 budget: &mut crate::budget::Budget<'_>,
524) -> Result<(), HostValueJsonError> {
525 let limit = budget.limits().max_value_nesting;
526 let mut depth = 0usize;
527 let mut in_string = false;
528 let mut escaped = false;
529 let mut next_checkpoint = 0usize;
533
534 for (index, byte) in input.as_bytes().iter().enumerate() {
535 if index == next_checkpoint {
536 budget
537 .checkpoint()
538 .map_err(|error| host_json_budget_error(error, "$"))?;
539 next_checkpoint = index.saturating_add(NESTING_CHECKPOINT_INTERVAL);
540 }
541 if in_string {
542 if escaped {
543 escaped = false;
544 } else if *byte == b'\\' {
545 escaped = true;
546 } else if *byte == b'"' {
547 in_string = false;
548 }
549 continue;
550 }
551 match byte {
552 b'"' => in_string = true,
553 b'{' | b'[' => {
554 depth = depth.saturating_add(1);
555 if depth > limit {
556 return Err(HostValueJsonError::ValueLimit {
557 resource: "value_nesting",
558 limit,
559 observed: depth,
560 path: "$".to_string(),
561 });
562 }
563 }
564 b'}' | b']' => depth = depth.saturating_sub(1),
565 _ => {}
566 }
567 }
568 Ok(())
569}
570
571#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
572#[serde(deny_unknown_fields)]
573struct RawHostFieldValue {
574 id: u32,
575 value: RawHostValue,
576}
577
578#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
579#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
580enum RawHostValue {
581 Null,
582 Bool { value: bool },
583 Nat { value: String },
584 Int { value: String },
585 Nat8 { value: u8 },
586 Nat16 { value: u16 },
587 Nat32 { value: u32 },
588 Nat64 { value: String },
589 Int8 { value: i8 },
590 Int16 { value: i16 },
591 Int32 { value: i32 },
592 Int64 { value: String },
593 Float32 { bits: String },
594 Float64 { bits: String },
595 Text { value: String },
596 Reserved,
597 Principal { value: String },
598 Opt { value: Option<Box<RawHostValue>> },
599 Vec { values: Vec<RawHostValue> },
600 Record { fields: Vec<RawHostFieldValue> },
601 Variant { id: u32, value: Box<RawHostValue> },
602 Service { principal: String },
603 Func { principal: String, method: String },
604}
605
606struct HostValueLocalValidationState<'a, 'limits> {
607 budget: &'a mut crate::budget::Budget<'limits>,
608}
609
610impl<'a, 'limits> HostValueLocalValidationState<'a, 'limits> {
611 fn new(budget: &'a mut crate::budget::Budget<'limits>) -> Self {
612 Self { budget }
613 }
614
615 fn canonicalize(mut self, raw: RawHostValue) -> Result<HostValue, HostValueJsonError> {
616 self.canonicalize_value(raw, "$", 0)
617 }
618
619 fn canonicalize_value(
620 &mut self,
621 raw: RawHostValue,
622 path: &str,
623 depth: usize,
624 ) -> Result<HostValue, HostValueJsonError> {
625 self.budget
626 .checkpoint()
627 .map_err(|error| host_json_budget_error(error, path))?;
628 let limits = self.budget.limits().clone();
629 if depth > limits.max_value_depth {
630 return Err(HostValueJsonError::ValueLimit {
631 resource: "value_depth",
632 limit: limits.max_value_depth,
633 observed: depth,
634 path: path.to_string(),
635 });
636 }
637 self.budget
638 .charge("value_elements", limits.max_value_elements, 1)
639 .map_err(|error| host_json_budget_error(error, path))?;
640 self.budget
641 .charge("canonicalization_work", limits.max_canonicalization_work, 1)
642 .map_err(|error| host_json_budget_error(error, path))?;
643
644 let value = match raw {
645 RawHostValue::Null => HostValueKind::Null,
646 RawHostValue::Bool { value } => HostValueKind::Bool { value },
647 RawHostValue::Nat { value } => {
648 self.charge(&value, path)?;
649 self.require(canonical_nat(&value), path, "non-canonical nat")?;
650 HostValueKind::Nat { value }
651 }
652 RawHostValue::Int { value } => {
653 self.charge(&value, path)?;
654 self.require(canonical_int(&value), path, "non-canonical int")?;
655 HostValueKind::Int { value }
656 }
657 RawHostValue::Nat8 { value } => HostValueKind::Nat8 { value },
658 RawHostValue::Nat16 { value } => HostValueKind::Nat16 { value },
659 RawHostValue::Nat32 { value } => HostValueKind::Nat32 { value },
660 RawHostValue::Nat64 { value } => {
661 self.charge(&value, path)?;
662 self.require(
663 canonical_nat(&value) && value.parse::<u64>().is_ok(),
664 path,
665 "non-canonical nat64",
666 )?;
667 HostValueKind::Nat64 { value }
668 }
669 RawHostValue::Int8 { value } => HostValueKind::Int8 { value },
670 RawHostValue::Int16 { value } => HostValueKind::Int16 { value },
671 RawHostValue::Int32 { value } => HostValueKind::Int32 { value },
672 RawHostValue::Int64 { value } => {
673 self.charge(&value, path)?;
674 self.require(
675 canonical_int(&value) && value.parse::<i64>().is_ok(),
676 path,
677 "non-canonical int64",
678 )?;
679 HostValueKind::Int64 { value }
680 }
681 RawHostValue::Float32 { bits } => {
682 self.charge(&bits, path)?;
683 self.require(canonical_hex(&bits, 8), path, "non-canonical float32 bits")?;
684 HostValueKind::Float32 { bits }
685 }
686 RawHostValue::Float64 { bits } => {
687 self.charge(&bits, path)?;
688 self.require(canonical_hex(&bits, 16), path, "non-canonical float64 bits")?;
689 HostValueKind::Float64 { bits }
690 }
691 RawHostValue::Text { value } => {
692 self.charge(&value, path)?;
693 HostValueKind::Text { value }
694 }
695 RawHostValue::Reserved => HostValueKind::Reserved,
696 RawHostValue::Principal { value } => {
697 self.charge(&value, path)?;
698 self.require_canonical_principal(&value, path)?;
699 HostValueKind::Principal { value }
700 }
701 RawHostValue::Opt { value } => HostValueKind::Opt {
702 value: value
703 .map(|value| {
704 self.canonicalize_value(*value, &format!("{path}.value"), depth + 1)
705 })
706 .transpose()?
707 .map(Box::new),
708 },
709 RawHostValue::Vec { values } => HostValueKind::Vec {
710 values: values
711 .into_iter()
712 .enumerate()
713 .map(|(index, value)| {
714 self.canonicalize_value(
715 value,
716 &format!("{path}.values[{index}]"),
717 depth + 1,
718 )
719 })
720 .collect::<Result<Vec<_>, _>>()?,
721 },
722 RawHostValue::Record { fields } => HostValueKind::Record {
723 fields: fields
724 .into_iter()
725 .map(|field| {
726 Ok(HostFieldValue::new(
727 field.id,
728 self.canonicalize_value(
729 field.value,
730 &format!("{path}.fields[{}]", field.id),
731 depth + 1,
732 )?,
733 ))
734 })
735 .collect::<Result<Vec<_>, HostValueJsonError>>()?,
736 },
737 RawHostValue::Variant { id, value } => HostValueKind::Variant {
738 id,
739 value: Box::new(self.canonicalize_value(
740 *value,
741 &format!("{path}.value"),
742 depth + 1,
743 )?),
744 },
745 RawHostValue::Service { principal } => {
746 self.charge(&principal, path)?;
747 self.require_canonical_principal(&principal, path)?;
748 HostValueKind::Service { principal }
749 }
750 RawHostValue::Func { principal, method } => {
751 self.charge(&principal, path)?;
752 self.charge(&method, path)?;
753 self.require_canonical_principal(&principal, path)?;
754 HostValueKind::Func { principal, method }
755 }
756 };
757 Ok(HostValue::measured(value))
758 }
759
760 fn charge(&mut self, value: &str, path: &str) -> Result<(), HostValueJsonError> {
761 let limit = self.budget.limits().max_value_bytes;
762 self.budget
763 .charge("value_bytes", limit, value.len())
764 .map_err(|error| host_json_budget_error(error, path))?;
765 Ok(())
766 }
767
768 fn require(
769 &self,
770 condition: bool,
771 path: &str,
772 message: &str,
773 ) -> Result<(), HostValueJsonError> {
774 if condition {
775 Ok(())
776 } else {
777 Err(HostValueJsonError::Malformed(format!("{path}: {message}")))
778 }
779 }
780
781 fn require_canonical_principal(
782 &self,
783 value: &str,
784 path: &str,
785 ) -> Result<(), HostValueJsonError> {
786 let principal = ic_principal::Principal::from_text(value).map_err(|error| {
787 HostValueJsonError::Malformed(format!("{path}: invalid principal {value:?}: {error}"))
788 })?;
789 self.require(
790 principal.to_text() == value,
791 path,
792 "non-canonical principal",
793 )
794 }
795}
796
797pub type HostValueViolation = crate::Diagnostic;
804
805#[derive(Debug, Clone, PartialEq, Eq)]
806pub struct HostValueValidationError {
807 pub violations: Vec<HostValueViolation>,
808}
809
810impl fmt::Display for HostValueValidationError {
811 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
812 write!(
813 formatter,
814 "HostValue validation failed with {} violation(s)",
815 self.violations.len()
816 )
817 }
818}
819
820impl std::error::Error for HostValueValidationError {}
821
822pub fn validate_host_value(
823 contract: &Contract,
824 selector: &ContractTypeRef,
825 value: &HostValue,
826 limits: &Limits,
827) -> Result<(), HostValueValidationError> {
828 validate_host_value_with_context(
829 contract,
830 selector,
831 value,
832 &crate::RuntimeContext::new(limits.clone()),
833 )
834}
835
836pub fn validate_host_value_with_context(
837 contract: &Contract,
838 selector: &ContractTypeRef,
839 value: &HostValue,
840 context: &crate::RuntimeContext,
841) -> Result<(), HostValueValidationError> {
842 if selector.contract_id != contract.contract_id() {
843 return Err(single(
844 "value_contract_id_mismatch",
845 "$",
846 format!(
847 "expected Contract {}, found {}",
848 contract.contract_id(),
849 selector.contract_id
850 ),
851 ));
852 }
853 if selector.type_ref as usize >= contract.types().len() {
854 return Err(single(
855 "value_type_ref_out_of_bounds",
856 "$",
857 format!(
858 "type reference {} is outside the Contract",
859 selector.type_ref
860 ),
861 ));
862 }
863
864 let mut budget = context.budget();
865 let mut state = HostValueValidationState {
866 contract,
867 budget: &mut budget,
868 };
869 state.validate_node(selector.type_ref, value, "$", 0)
870}
871
872struct HostValueValidationState<'a, 'budget, 'limits> {
873 contract: &'a Contract,
874 budget: &'budget mut crate::budget::Budget<'limits>,
875}
876
877impl HostValueValidationState<'_, '_, '_> {
878 fn validate_node(
879 &mut self,
880 reference: TypeRef,
881 value: &HostValue,
882 path: &str,
883 depth: usize,
884 ) -> Result<(), HostValueValidationError> {
885 self.budget
886 .checkpoint()
887 .map_err(|error| host_validation_budget_error(error, path))?;
888 let limits = self.budget.limits().clone();
889 if depth > limits.max_value_depth {
890 return Err(resource_single(
891 "value_depth",
892 limits.max_value_depth,
893 depth,
894 path,
895 format!("value depth exceeds limit {}", limits.max_value_depth),
896 ));
897 }
898
899 self.charge_element(path)?;
900 self.charge_string_bytes(value, path)?;
901
902 match (&self.contract.types()[reference as usize], &value.kind) {
903 (TypeNode::Primitive { primitive }, value) => {
904 validate_primitive(*primitive, value, path)?;
905 }
906 (TypeNode::Opt { inner }, HostValueKind::Opt { value }) => {
907 if let Some(value) = value {
908 self.preflight_children(1, path)?;
909 let child_path = format!("{path}.value");
910 self.validate_node(*inner, value, &child_path, depth + 1)?;
911 }
912 }
913 (TypeNode::Vec { inner }, HostValueKind::Vec { values }) => {
914 self.preflight_children(values.len(), path)?;
915 for (index, value) in values.iter().enumerate() {
916 let child_path = format!("{path}.values[{index}]");
917 self.validate_node(*inner, value, &child_path, depth + 1)?;
918 }
919 }
920 (TypeNode::Record { fields }, HostValueKind::Record { fields: values }) => {
921 self.preflight_children(values.len(), path)?;
922 for (index, field) in values.iter().enumerate() {
923 for other in &values[index + 1..] {
924 self.charge_work(path)?;
925 if other.id == field.id {
926 return Err(single(
927 "duplicate_host_field",
928 path,
929 format!("record field ID {} occurs more than once", field.id),
930 ));
931 }
932 }
933 }
934 let mut field_set_matches = fields.len() == values.len();
935 if field_set_matches {
936 'expected_fields: for field in fields {
937 for value in values {
938 self.charge_work(path)?;
939 if value.id == field.id {
940 continue 'expected_fields;
941 }
942 }
943 field_set_matches = false;
944 break;
945 }
946 }
947 if !field_set_matches {
948 let expected_ids =
949 self.sorted_field_ids(fields.len(), |index| fields[index].id, path)?;
950 let actual_ids =
951 self.sorted_field_ids(values.len(), |index| values[index].id, path)?;
952 return Err(single(
953 "record_field_set_mismatch",
954 path,
955 format!("expected field IDs {}, found {}", expected_ids, actual_ids),
956 ));
957 }
958 for field in fields {
959 let mut matching_value = None;
960 for value in values {
961 self.charge_work(path)?;
962 if value.id == field.id {
963 matching_value = Some(value);
964 break;
965 }
966 }
967 let value = matching_value.expect("record field set was checked above");
968 let child_path = format!("{path}.fields[{}]", field.id);
969 self.validate_node(field.ty, &value.value, &child_path, depth + 1)?;
970 }
971 }
972 (TypeNode::Variant { fields }, HostValueKind::Variant { id, value }) => {
973 let mut matched = None;
980 for field in fields {
981 self.charge_work(path)?;
982 if field.id == *id {
983 matched = Some(field);
984 break;
985 }
986 }
987 let Some(field) = matched else {
988 return Err(single(
989 "unknown_variant_id",
990 path,
991 format!("variant ID {id} does not exist in the expected type"),
992 ));
993 };
994 self.preflight_children(1, path)?;
995 let child_path = format!("{path}.value");
996 self.validate_node(field.ty, value, &child_path, depth + 1)?;
997 }
998 (TypeNode::Service { .. }, HostValueKind::Service { principal }) => {
999 validate_principal(principal, path)?;
1000 }
1001 (TypeNode::Func { .. }, HostValueKind::Func { principal, method }) => {
1002 validate_principal(principal, path)?;
1003 if method.is_empty() {
1004 return Err(single(
1005 "empty_function_method",
1006 path,
1007 "function method names must not be empty",
1008 ));
1009 }
1010 }
1011 (TypeNode::Class { .. }, _) => {
1012 return Err(single(
1013 "class_has_no_host_value",
1014 path,
1015 "service constructors are not first-class Candid values",
1016 ));
1017 }
1018 (expected, actual) => {
1019 return Err(single(
1020 "host_value_kind_mismatch",
1021 path,
1022 format!(
1023 "expected {}, found {}",
1024 type_node_kind(expected),
1025 host_value_kind(actual)
1026 ),
1027 ));
1028 }
1029 }
1030 Ok(())
1031 }
1032
1033 fn charge_element(&mut self, path: &str) -> Result<(), HostValueValidationError> {
1034 let limit = self.budget.limits().max_value_elements;
1035 self.budget
1036 .charge("value_elements", limit, 1)
1037 .map_err(|error| host_validation_budget_error(error, path))?;
1038 Ok(())
1039 }
1040
1041 fn check_deadline(&self, path: &str) -> Result<(), HostValueValidationError> {
1042 self.budget
1043 .checkpoint()
1044 .map_err(|error| host_validation_budget_error(error, path))
1045 }
1046
1047 fn charge_work(&mut self, path: &str) -> Result<(), HostValueValidationError> {
1048 self.check_deadline(path)?;
1049 let limit = self.budget.limits().max_canonicalization_work;
1050 self.budget
1051 .charge("canonicalization_work", limit, 1)
1052 .map_err(|error| host_validation_budget_error(error, path))?;
1053 Ok(())
1054 }
1055
1056 fn preflight_children(
1057 &mut self,
1058 child_count: usize,
1059 path: &str,
1060 ) -> Result<(), HostValueValidationError> {
1061 let limit = self.budget.limits().max_value_elements;
1062 let observed = self
1063 .budget
1064 .consumed("value_elements")
1065 .saturating_add(child_count);
1066 if observed > limit {
1067 return Err(resource_single(
1068 "value_elements",
1069 limit,
1070 observed,
1071 path,
1072 format!("value elements exceed limit {limit}"),
1073 ));
1074 }
1075 Ok(())
1076 }
1077
1078 fn charge_string_bytes(
1079 &mut self,
1080 value: &HostValue,
1081 path: &str,
1082 ) -> Result<(), HostValueValidationError> {
1083 let limit = self.budget.limits().max_value_bytes;
1084 self.budget
1085 .charge("value_bytes", limit, value_string_bytes(value))
1086 .map_err(|error| host_validation_budget_error(error, path))?;
1087 Ok(())
1088 }
1089
1090 fn sorted_field_ids(
1091 &mut self,
1092 length: usize,
1093 id_at: impl Fn(usize) -> u32,
1094 path: &str,
1095 ) -> Result<String, HostValueValidationError> {
1096 let mut output = String::from("[");
1097 let mut previous = None;
1098 for position in 0..length {
1099 let mut next = None;
1100 for index in 0..length {
1101 self.charge_work(path)?;
1102 let id = id_at(index);
1103 let after_previous = previous.map_or(true, |previous| id > previous);
1104 let before_next = next.map_or(true, |next| id < next);
1105 if after_previous && before_next {
1106 next = Some(id);
1107 }
1108 }
1109 let Some(id) = next else {
1110 break;
1111 };
1112 if position > 0 {
1113 output.push_str(", ");
1114 }
1115 output.push_str(&id.to_string());
1116 previous = Some(id);
1117 }
1118 output.push(']');
1119 Ok(output)
1120 }
1121}
1122
1123fn validate_primitive(
1124 primitive: PrimitiveType,
1125 value: &HostValueKind,
1126 path: &str,
1127) -> Result<(), HostValueValidationError> {
1128 let valid = match (primitive, value) {
1129 (PrimitiveType::Null, HostValueKind::Null)
1130 | (PrimitiveType::Bool, HostValueKind::Bool { .. })
1131 | (PrimitiveType::Nat8, HostValueKind::Nat8 { .. })
1132 | (PrimitiveType::Nat16, HostValueKind::Nat16 { .. })
1133 | (PrimitiveType::Nat32, HostValueKind::Nat32 { .. })
1134 | (PrimitiveType::Int8, HostValueKind::Int8 { .. })
1135 | (PrimitiveType::Int16, HostValueKind::Int16 { .. })
1136 | (PrimitiveType::Int32, HostValueKind::Int32 { .. })
1137 | (PrimitiveType::Reserved, HostValueKind::Reserved) => true,
1138 (PrimitiveType::Nat, HostValueKind::Nat { value }) => canonical_nat(value),
1139 (PrimitiveType::Int, HostValueKind::Int { value }) => canonical_int(value),
1140 (PrimitiveType::Nat64, HostValueKind::Nat64 { value }) => {
1141 canonical_nat(value) && value.parse::<u64>().is_ok()
1142 }
1143 (PrimitiveType::Int64, HostValueKind::Int64 { value }) => {
1144 canonical_int(value) && value.parse::<i64>().is_ok()
1145 }
1146 (PrimitiveType::Float32, HostValueKind::Float32 { bits }) => canonical_hex(bits, 8),
1147 (PrimitiveType::Float64, HostValueKind::Float64 { bits }) => canonical_hex(bits, 16),
1148 (PrimitiveType::Text, HostValueKind::Text { .. }) => true,
1149 (PrimitiveType::Principal, HostValueKind::Principal { value }) => {
1150 validate_principal(value, path)?;
1151 true
1152 }
1153 (PrimitiveType::Empty, _) => {
1154 return Err(single(
1155 "empty_has_no_value",
1156 path,
1157 "the Candid empty type has no constructible HostValue",
1158 ));
1159 }
1160 _ => false,
1161 };
1162 if valid {
1163 Ok(())
1164 } else {
1165 Err(single(
1166 "host_value_kind_mismatch",
1167 path,
1168 format!(
1169 "expected primitive {primitive:?}, found {} or a non-canonical representation",
1170 host_value_kind(value)
1171 ),
1172 ))
1173 }
1174}
1175
1176fn value_string_bytes(value: &HostValue) -> usize {
1177 match &value.kind {
1178 HostValueKind::Nat { value }
1179 | HostValueKind::Int { value }
1180 | HostValueKind::Nat64 { value }
1181 | HostValueKind::Int64 { value }
1182 | HostValueKind::Text { value }
1183 | HostValueKind::Principal { value } => value.len(),
1184 HostValueKind::Float32 { bits } | HostValueKind::Float64 { bits } => bits.len(),
1185 HostValueKind::Service { principal } => principal.len(),
1186 HostValueKind::Func { principal, method } => principal.len().saturating_add(method.len()),
1187 _ => 0,
1188 }
1189}
1190
1191fn canonical_nat(value: &str) -> bool {
1192 value == "0"
1193 || (!value.starts_with('0')
1194 && !value.is_empty()
1195 && value.bytes().all(|byte| byte.is_ascii_digit()))
1196}
1197
1198fn canonical_int(value: &str) -> bool {
1199 if let Some(magnitude) = value.strip_prefix('-') {
1200 magnitude != "0" && canonical_nat(magnitude)
1201 } else {
1202 canonical_nat(value)
1203 }
1204}
1205
1206fn canonical_hex(value: &str, length: usize) -> bool {
1207 value.len() == length
1208 && value
1209 .bytes()
1210 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
1211}
1212
1213fn validate_principal(value: &str, path: &str) -> Result<(), HostValueValidationError> {
1214 let principal = ic_principal::Principal::from_text(value).map_err(|error| {
1215 single(
1216 "invalid_principal",
1217 path,
1218 format!("invalid principal {value:?}: {error}"),
1219 )
1220 })?;
1221 if principal.to_text() != value {
1222 return Err(single(
1223 "invalid_principal",
1224 path,
1225 format!("principal {value:?} is not in canonical textual form"),
1226 ));
1227 }
1228 Ok(())
1229}
1230
1231fn single(
1232 code: impl Into<String>,
1233 path: impl Into<String>,
1234 message: impl Into<String>,
1235) -> HostValueValidationError {
1236 HostValueValidationError {
1237 violations: vec![crate::Diagnostic::violation(code, path, message)],
1238 }
1239}
1240
1241fn resource_single(
1242 resource: &str,
1243 limit: usize,
1244 observed: usize,
1245 path: impl Into<String>,
1246 message: impl Into<String>,
1247) -> HostValueValidationError {
1248 HostValueValidationError {
1249 violations: vec![
1250 crate::Diagnostic::violation("resource_limit_exceeded", path, message)
1251 .with_resource_limit(crate::ResourceLimitInfo {
1252 resource: resource.to_string(),
1253 limit: crate::limits::portable_count(limit),
1254 observed: crate::limits::portable_count(observed),
1255 }),
1256 ],
1257 }
1258}
1259
1260fn host_validation_budget_error(
1261 error: crate::budget::BudgetError,
1262 path: &str,
1263) -> HostValueValidationError {
1264 match error {
1265 crate::budget::BudgetError::Cancelled => single(
1266 "operation_cancelled",
1267 path,
1268 "HostValue validation was cancelled",
1269 ),
1270 crate::budget::BudgetError::DeadlineExceeded => single(
1271 "operation_deadline_exceeded",
1272 path,
1273 "HostValue validation deadline has elapsed",
1274 ),
1275 crate::budget::BudgetError::ResourceLimit {
1276 resource,
1277 limit,
1278 observed,
1279 } => resource_single(
1280 resource,
1281 limit,
1282 observed,
1283 path,
1284 format!("resource {resource} exceeded limit {limit}; observed {observed}"),
1285 ),
1286 }
1287}
1288
1289fn type_node_kind(node: &TypeNode) -> &'static str {
1290 match node {
1291 TypeNode::Primitive { .. } => "primitive",
1292 TypeNode::Opt { .. } => "opt",
1293 TypeNode::Vec { .. } => "vec",
1294 TypeNode::Record { .. } => "record",
1295 TypeNode::Variant { .. } => "variant",
1296 TypeNode::Func { .. } => "func",
1297 TypeNode::Service { .. } => "service",
1298 TypeNode::Class { .. } => "class",
1299 }
1300}
1301
1302fn host_value_kind(value: &HostValueKind) -> &'static str {
1303 match value {
1304 HostValueKind::Null => "null",
1305 HostValueKind::Bool { .. } => "bool",
1306 HostValueKind::Nat { .. } => "nat",
1307 HostValueKind::Int { .. } => "int",
1308 HostValueKind::Nat8 { .. } => "nat8",
1309 HostValueKind::Nat16 { .. } => "nat16",
1310 HostValueKind::Nat32 { .. } => "nat32",
1311 HostValueKind::Nat64 { .. } => "nat64",
1312 HostValueKind::Int8 { .. } => "int8",
1313 HostValueKind::Int16 { .. } => "int16",
1314 HostValueKind::Int32 { .. } => "int32",
1315 HostValueKind::Int64 { .. } => "int64",
1316 HostValueKind::Float32 { .. } => "float32",
1317 HostValueKind::Float64 { .. } => "float64",
1318 HostValueKind::Text { .. } => "text",
1319 HostValueKind::Reserved => "reserved",
1320 HostValueKind::Principal { .. } => "principal",
1321 HostValueKind::Opt { .. } => "opt",
1322 HostValueKind::Vec { .. } => "vec",
1323 HostValueKind::Record { .. } => "record",
1324 HostValueKind::Variant { .. } => "variant",
1325 HostValueKind::Service { .. } => "service",
1326 HostValueKind::Func { .. } => "func",
1327 }
1328}
1329
1330impl Contract {
1331 pub fn bind_type(
1332 &self,
1333 type_ref: TypeRef,
1334 ) -> Result<ContractTypeRef, HostValueValidationError> {
1335 if type_ref as usize >= self.types().len() {
1336 return Err(single(
1337 "value_type_ref_out_of_bounds",
1338 "$",
1339 format!("type reference {type_ref} is outside the Contract"),
1340 ));
1341 }
1342 Ok(ContractTypeRef {
1343 contract_id: self.contract_id().to_string(),
1344 type_ref,
1345 })
1346 }
1347
1348 pub fn bind_method(
1349 &self,
1350 method: impl Into<String>,
1351 ) -> Result<ContractMethodRef, HostValueValidationError> {
1352 let method = method.into();
1353 let service = match self.actor() {
1354 Some(crate::model::Actor::Service { service }) => *service,
1355 Some(crate::model::Actor::Class { class }) => match &self.types()[*class as usize] {
1356 TypeNode::Class { service, .. } => *service,
1357 _ => unreachable!("validated class actor targets a class"),
1358 },
1359 None => {
1360 return Err(single(
1361 "actorless_contract",
1362 "$",
1363 "an actorless Contract has no methods",
1364 ));
1365 }
1366 };
1367 match &self.types()[service as usize] {
1368 TypeNode::Service { methods } if methods.iter().any(|entry| entry.name == method) => {
1369 Ok(ContractMethodRef {
1370 contract_id: self.contract_id().to_string(),
1371 method_name: method,
1372 })
1373 }
1374 _ => Err(single(
1375 "unknown_method",
1376 "$",
1377 format!("method {method:?} does not exist in the actor service"),
1378 )),
1379 }
1380 }
1381}