use std::fmt;
use std::sync::Arc;
use rustev_contract::remote::WirePrincipal;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EndpointOperator {
#[default]
ThirdParty,
HostOperated,
}
pub trait PrincipalSource: Send + Sync {
fn reference(&self, principal_handle: &[u8]) -> Option<Vec<u8>>;
}
pub struct HandleAsReference;
impl PrincipalSource for HandleAsReference {
fn reference(&self, principal_handle: &[u8]) -> Option<Vec<u8>> {
(!principal_handle.is_empty()).then(|| principal_handle.to_vec())
}
}
#[derive(Clone, Default)]
pub enum PrincipalPosture {
#[default]
Omitted,
Sent {
mapping_version: String,
scope: String,
source: Arc<dyn PrincipalSource>,
},
}
impl fmt::Debug for PrincipalPosture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrincipalPosture::Omitted => f.write_str("Omitted"),
PrincipalPosture::Sent {
mapping_version,
scope,
..
} => f
.debug_struct("Sent")
.field("mapping_version", mapping_version)
.field("scope", scope)
.finish_non_exhaustive(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PrincipalWire {
#[default]
Omitted,
HostOperated {
mapping_version: String,
scope: String,
},
}
impl PrincipalWire {
pub fn is_omitted(&self) -> bool {
matches!(self, PrincipalWire::Omitted)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrincipalConfigError {
ThirdPartyEndpoint,
NeedsProtocolV2,
BadLabel,
}
impl fmt::Display for PrincipalConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
PrincipalConfigError::ThirdPartyEndpoint => {
"a principal reference is never sent to a third-party endpoint"
}
PrincipalConfigError::NeedsProtocolV2 => "a principal reference needs rustev.remote/2",
PrincipalConfigError::BadLabel => {
"the principal mapping version and scope must be 1 to 64 printable ASCII bytes"
}
})
}
}
impl std::error::Error for PrincipalConfigError {}
impl PrincipalPosture {
pub fn validate(
&self,
operator: EndpointOperator,
protocol_major: u32,
) -> Result<PrincipalWire, PrincipalConfigError> {
match self {
PrincipalPosture::Omitted => Ok(PrincipalWire::Omitted),
PrincipalPosture::Sent {
mapping_version,
scope,
..
} => {
if operator != EndpointOperator::HostOperated {
return Err(PrincipalConfigError::ThirdPartyEndpoint);
}
if protocol_major != rustev_contract::remote::PROTOCOL_MAJOR_V2 {
return Err(PrincipalConfigError::NeedsProtocolV2);
}
if WirePrincipal::encode(&[0], mapping_version, scope).is_none() {
return Err(PrincipalConfigError::BadLabel);
}
Ok(PrincipalWire::HostOperated {
mapping_version: mapping_version.clone(),
scope: scope.clone(),
})
}
}
}
#[allow(clippy::result_unit_err)]
pub fn for_attempt(&self, principal_handle: &[u8]) -> Result<Option<WirePrincipal>, ()> {
match self {
PrincipalPosture::Omitted => Ok(None),
PrincipalPosture::Sent {
mapping_version,
scope,
source,
} => source
.reference(principal_handle)
.and_then(|r| WirePrincipal::encode(&r, mapping_version, scope))
.map(Some)
.ok_or(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustev_contract::remote::MAX_PRINCIPAL_BYTES;
fn sent() -> PrincipalPosture {
PrincipalPosture::Sent {
mapping_version: "synthetic-map/1".into(),
scope: "synthetic-tenant-routing".into(),
source: Arc::new(HandleAsReference),
}
}
#[test]
fn only_a_host_operated_endpoint_over_version_2_may_receive_it() {
assert_eq!(
PrincipalPosture::Omitted.validate(EndpointOperator::ThirdParty, 1),
Ok(PrincipalWire::Omitted)
);
assert_eq!(
sent().validate(EndpointOperator::ThirdParty, 2),
Err(PrincipalConfigError::ThirdPartyEndpoint)
);
assert_eq!(
sent().validate(EndpointOperator::HostOperated, 1),
Err(PrincipalConfigError::NeedsProtocolV2)
);
assert!(sent().validate(EndpointOperator::HostOperated, 2).is_ok());
for (m, s) in [
("", "s"),
("m", ""),
("m m", "s"),
(&"x".repeat(65)[..], "s"),
] {
let p = PrincipalPosture::Sent {
mapping_version: m.into(),
scope: s.into(),
source: Arc::new(HandleAsReference),
};
assert_eq!(
p.validate(EndpointOperator::HostOperated, 2),
Err(PrincipalConfigError::BadLabel)
);
}
}
#[test]
fn a_missing_or_oversized_reference_is_refused_and_nothing_prints_it() {
assert_eq!(PrincipalPosture::Omitted.for_attempt(b"h"), Ok(None));
assert_eq!(sent().for_attempt(b""), Err(()));
assert_eq!(sent().for_attempt(&[7; MAX_PRINCIPAL_BYTES + 1]), Err(()));
let p = sent()
.for_attempt(&[7; MAX_PRINCIPAL_BYTES])
.unwrap()
.unwrap();
assert_eq!(p.reference_bytes().unwrap(), vec![7; MAX_PRINCIPAL_BYTES]);
let w = sent().for_attempt(b"SYNTHETIC-principal").unwrap().unwrap();
assert!(!format!("{w:?}").contains(&w.reference));
assert!(!format!("{:?}", sent()).contains("SYNTHETIC"));
}
}