ic_backup/model/application_ingress/payload/
mod.rs1use super::ApplicationIngressError;
4use crate::model::{
5 artifacts::ArtifactChecksumRecord, attempt_journal::OperationBindingRecord,
6 evidence_digest::EvidenceDigest,
7};
8use std::fmt;
9
10pub const MAX_APPLICATION_INGRESS_ARGUMENT_BYTES: usize = 1024 * 1024;
12pub const MAX_APPLICATION_INGRESS_METHOD_BYTES: usize = 128;
14pub const MAX_APPLICATION_INGRESS_REPLY_BYTES: usize = 1024 * 1024;
16
17#[derive(Clone)]
25pub struct ApplicationIngressPayload {
26 receiver: String,
27 target: String,
28 receiver_bytes: Vec<u8>,
29 target_bytes: Vec<u8>,
30 method: String,
31 arguments: Vec<u8>,
32 reply_limit: usize,
33}
34
35impl ApplicationIngressPayload {
36 pub fn new(
40 receiver: &str,
41 target: &str,
42 method: &str,
43 arguments: &[u8],
44 reply_limit: usize,
45 ) -> Result<Self, ApplicationIngressError> {
46 if arguments.len() > MAX_APPLICATION_INGRESS_ARGUMENT_BYTES {
47 return Err(ApplicationIngressError::ArgumentsTooLarge);
48 }
49 if method.is_empty()
50 || method.len() > MAX_APPLICATION_INGRESS_METHOD_BYTES
51 || !method.bytes().all(|byte| byte.is_ascii_graphic())
52 {
53 return Err(ApplicationIngressError::InvalidMethod);
54 }
55 if !(1..=MAX_APPLICATION_INGRESS_REPLY_BYTES).contains(&reply_limit) {
56 return Err(ApplicationIngressError::InvalidReplyLimit);
57 }
58 let admit = |value| {
59 let (text, principal) = crate::model::principal::canonical_parts(value)
60 .ok_or(ApplicationIngressError::InvalidPrincipal)?;
61 if principal.as_slice().is_empty() {
62 return Err(ApplicationIngressError::ManagementPrincipal);
63 }
64 Ok((text, principal.as_slice().to_vec()))
65 };
66 let (receiver, receiver_bytes) = admit(receiver)?;
67 let (target, target_bytes) = admit(target)?;
68 Ok(Self {
69 receiver,
70 target,
71 receiver_bytes,
72 target_bytes,
73 method: method.into(),
74 arguments: arguments.into(),
75 reply_limit,
76 })
77 }
78 #[must_use]
80 pub fn receiver(&self) -> &str {
81 &self.receiver
82 }
83 #[must_use]
85 pub fn target(&self) -> &str {
86 &self.target
87 }
88 #[must_use]
90 pub fn method(&self) -> &str {
91 &self.method
92 }
93 #[must_use]
95 pub fn arguments(&self) -> &[u8] {
96 &self.arguments
97 }
98 #[must_use]
100 pub const fn reply_limit(&self) -> usize {
101 self.reply_limit
102 }
103 #[must_use]
106 pub fn digest(&self) -> ArtifactChecksumRecord {
107 let mut framing = EvidenceDigest::new(b"ic-backup/application-ingress/v1\0");
108 for principal in [&self.receiver_bytes, &self.target_bytes] {
109 framing.byte(principal.len().to_le_bytes()[0]);
110 framing.append(principal);
111 }
112 framing.byte(1);
113 for bytes in [self.method.as_bytes(), self.arguments.as_slice()] {
114 framing.u64_be(bytes.len() as u64);
115 framing.append(bytes);
116 }
117 framing.u64_be(self.reply_limit as u64);
118 framing.finish()
119 }
120 pub fn validate_mutation_binding(
124 &self,
125 binding: &OperationBindingRecord,
126 ) -> Result<(), ApplicationIngressError> {
127 self.validate_target(binding)?;
128 if self.digest().hash() != binding.request() {
129 return Err(ApplicationIngressError::PayloadMismatch);
130 }
131 Ok(())
132 }
133 pub fn validate_observation_binding(
140 &self,
141 binding: &OperationBindingRecord,
142 ) -> Result<(), ApplicationIngressError> {
143 self.validate_target(binding)
144 }
145 fn validate_target(
146 &self,
147 binding: &OperationBindingRecord,
148 ) -> Result<(), ApplicationIngressError> {
149 if self.target != binding.target() {
150 return Err(ApplicationIngressError::TargetMismatch);
151 }
152 Ok(())
153 }
154}
155
156impl fmt::Debug for ApplicationIngressPayload {
157 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
158 formatter
159 .debug_struct("ApplicationIngressPayload")
160 .field("receiver", &self.receiver)
161 .field("target", &self.target)
162 .field("method", &self.method)
163 .field("argument_bytes", &self.arguments.len())
164 .field("reply_limit", &self.reply_limit)
165 .finish_non_exhaustive()
166 }
167}