1use std::fmt;
16use std::sync::Arc;
17
18use rustev_contract::remote::WirePrincipal;
19use serde::Serialize;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum EndpointOperator {
24 #[default]
27 ThirdParty,
28 HostOperated,
30}
31
32pub trait PrincipalSource: Send + Sync {
34 fn reference(&self, principal_handle: &[u8]) -> Option<Vec<u8>>;
38}
39
40pub struct HandleAsReference;
44
45impl PrincipalSource for HandleAsReference {
46 fn reference(&self, principal_handle: &[u8]) -> Option<Vec<u8>> {
47 (!principal_handle.is_empty()).then(|| principal_handle.to_vec())
48 }
49}
50
51#[derive(Clone, Default)]
53pub enum PrincipalPosture {
54 #[default]
56 Omitted,
57 Sent {
59 mapping_version: String,
60 scope: String,
61 source: Arc<dyn PrincipalSource>,
62 },
63}
64
65impl fmt::Debug for PrincipalPosture {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 PrincipalPosture::Omitted => f.write_str("Omitted"),
69 PrincipalPosture::Sent {
70 mapping_version,
71 scope,
72 ..
73 } => f
74 .debug_struct("Sent")
75 .field("mapping_version", mapping_version)
76 .field("scope", scope)
77 .finish_non_exhaustive(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
86#[serde(rename_all = "snake_case")]
87pub enum PrincipalWire {
88 #[default]
89 Omitted,
90 HostOperated {
91 mapping_version: String,
92 scope: String,
93 },
94}
95
96impl PrincipalWire {
97 pub fn is_omitted(&self) -> bool {
98 matches!(self, PrincipalWire::Omitted)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum PrincipalConfigError {
105 ThirdPartyEndpoint,
107 NeedsProtocolV2,
109 BadLabel,
111}
112
113impl fmt::Display for PrincipalConfigError {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.write_str(match self {
116 PrincipalConfigError::ThirdPartyEndpoint => {
117 "a principal reference is never sent to a third-party endpoint"
118 }
119 PrincipalConfigError::NeedsProtocolV2 => "a principal reference needs rustev.remote/2",
120 PrincipalConfigError::BadLabel => {
121 "the principal mapping version and scope must be 1 to 64 printable ASCII bytes"
122 }
123 })
124 }
125}
126
127impl std::error::Error for PrincipalConfigError {}
128
129impl PrincipalPosture {
130 pub fn validate(
133 &self,
134 operator: EndpointOperator,
135 protocol_major: u32,
136 ) -> Result<PrincipalWire, PrincipalConfigError> {
137 match self {
138 PrincipalPosture::Omitted => Ok(PrincipalWire::Omitted),
139 PrincipalPosture::Sent {
140 mapping_version,
141 scope,
142 ..
143 } => {
144 if operator != EndpointOperator::HostOperated {
145 return Err(PrincipalConfigError::ThirdPartyEndpoint);
146 }
147 if protocol_major != rustev_contract::remote::PROTOCOL_MAJOR_V2 {
148 return Err(PrincipalConfigError::NeedsProtocolV2);
149 }
150 if WirePrincipal::encode(&[0], mapping_version, scope).is_none() {
152 return Err(PrincipalConfigError::BadLabel);
153 }
154 Ok(PrincipalWire::HostOperated {
155 mapping_version: mapping_version.clone(),
156 scope: scope.clone(),
157 })
158 }
159 }
160 }
161
162 #[allow(clippy::result_unit_err)]
166 pub fn for_attempt(&self, principal_handle: &[u8]) -> Result<Option<WirePrincipal>, ()> {
167 match self {
168 PrincipalPosture::Omitted => Ok(None),
169 PrincipalPosture::Sent {
170 mapping_version,
171 scope,
172 source,
173 } => source
174 .reference(principal_handle)
175 .and_then(|r| WirePrincipal::encode(&r, mapping_version, scope))
176 .map(Some)
177 .ok_or(()),
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use rustev_contract::remote::MAX_PRINCIPAL_BYTES;
186
187 fn sent() -> PrincipalPosture {
188 PrincipalPosture::Sent {
189 mapping_version: "synthetic-map/1".into(),
190 scope: "synthetic-tenant-routing".into(),
191 source: Arc::new(HandleAsReference),
192 }
193 }
194
195 #[test]
196 fn only_a_host_operated_endpoint_over_version_2_may_receive_it() {
197 assert_eq!(
198 PrincipalPosture::Omitted.validate(EndpointOperator::ThirdParty, 1),
199 Ok(PrincipalWire::Omitted)
200 );
201 assert_eq!(
202 sent().validate(EndpointOperator::ThirdParty, 2),
203 Err(PrincipalConfigError::ThirdPartyEndpoint)
204 );
205 assert_eq!(
206 sent().validate(EndpointOperator::HostOperated, 1),
207 Err(PrincipalConfigError::NeedsProtocolV2)
208 );
209 assert!(sent().validate(EndpointOperator::HostOperated, 2).is_ok());
210 for (m, s) in [
211 ("", "s"),
212 ("m", ""),
213 ("m m", "s"),
214 (&"x".repeat(65)[..], "s"),
215 ] {
216 let p = PrincipalPosture::Sent {
217 mapping_version: m.into(),
218 scope: s.into(),
219 source: Arc::new(HandleAsReference),
220 };
221 assert_eq!(
222 p.validate(EndpointOperator::HostOperated, 2),
223 Err(PrincipalConfigError::BadLabel)
224 );
225 }
226 }
227
228 #[test]
229 fn a_missing_or_oversized_reference_is_refused_and_nothing_prints_it() {
230 assert_eq!(PrincipalPosture::Omitted.for_attempt(b"h"), Ok(None));
231 assert_eq!(sent().for_attempt(b""), Err(()));
232 assert_eq!(sent().for_attempt(&[7; MAX_PRINCIPAL_BYTES + 1]), Err(()));
233 let p = sent()
234 .for_attempt(&[7; MAX_PRINCIPAL_BYTES])
235 .unwrap()
236 .unwrap();
237 assert_eq!(p.reference_bytes().unwrap(), vec![7; MAX_PRINCIPAL_BYTES]);
238 let w = sent().for_attempt(b"SYNTHETIC-principal").unwrap().unwrap();
239 assert!(!format!("{w:?}").contains(&w.reference));
240 assert!(!format!("{:?}", sent()).contains("SYNTHETIC"));
241 }
242}