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 (receiver, receiver_bytes) = principal(receiver)?;
59 let (target, target_bytes) = principal(target)?;
60 Ok(Self {
61 receiver,
62 target,
63 receiver_bytes,
64 target_bytes,
65 method: method.into(),
66 arguments: arguments.into(),
67 reply_limit,
68 })
69 }
70 #[must_use]
72 pub fn receiver(&self) -> &str {
73 &self.receiver
74 }
75 #[must_use]
77 pub fn target(&self) -> &str {
78 &self.target
79 }
80 #[must_use]
82 pub fn method(&self) -> &str {
83 &self.method
84 }
85 #[must_use]
87 pub fn arguments(&self) -> &[u8] {
88 &self.arguments
89 }
90 #[must_use]
92 pub const fn reply_limit(&self) -> usize {
93 self.reply_limit
94 }
95 #[must_use]
98 pub fn digest(&self) -> ArtifactChecksumRecord {
99 let mut framing = EvidenceDigest::new(b"ic-backup/application-ingress/v1\0");
100 for principal in [&self.receiver_bytes, &self.target_bytes] {
101 framing.bytes_u8(principal);
102 }
103 framing.byte(1);
104 for bytes in [self.method.as_bytes(), self.arguments.as_slice()] {
105 framing.bytes_u64(bytes);
106 }
107 framing.u64_be(self.reply_limit as u64);
108 framing.finish()
109 }
110 pub fn validate_mutation_binding(
114 &self,
115 binding: &OperationBindingRecord,
116 ) -> Result<(), ApplicationIngressError> {
117 self.validate_target(binding)?;
118 if self.digest().hash() != binding.request() {
119 return Err(ApplicationIngressError::PayloadMismatch);
120 }
121 Ok(())
122 }
123 pub fn validate_observation_binding(
130 &self,
131 binding: &OperationBindingRecord,
132 ) -> Result<(), ApplicationIngressError> {
133 self.validate_target(binding)
134 }
135 fn validate_target(
136 &self,
137 binding: &OperationBindingRecord,
138 ) -> Result<(), ApplicationIngressError> {
139 if self.target != binding.target() {
140 return Err(ApplicationIngressError::TargetMismatch);
141 }
142 Ok(())
143 }
144}
145
146impl fmt::Debug for ApplicationIngressPayload {
147 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148 formatter
149 .debug_struct("ApplicationIngressPayload")
150 .field("receiver", &self.receiver)
151 .field("target", &self.target)
152 .field("method", &self.method)
153 .field("argument_bytes", &self.arguments.len())
154 .field("reply_limit", &self.reply_limit)
155 .finish_non_exhaustive()
156 }
157}
158
159fn principal(value: &str) -> Result<(String, Vec<u8>), ApplicationIngressError> {
160 let (text, principal) = crate::model::principal::canonical_parts(value)
161 .ok_or(ApplicationIngressError::InvalidPrincipal)?;
162 if principal.as_slice().is_empty() {
163 return Err(ApplicationIngressError::ManagementPrincipal);
164 }
165 Ok((text, principal.as_slice().to_vec()))
166}