use std::sync::Arc;
use super::behavior::admission_clock::ClockSample;
use super::behavior::org::OrgId;
use super::behavior::org_admission::{AdmissionDenied, OrgAdmission};
use super::behavior::org_authority::NodeAuthority;
use super::behavior::org_call::{OrgCallProof, ORG_ADMISSION_HEADER};
use super::behavior::org_revocation::{
BarrieredGeneration, OrgRevocationState, OrgRevocationStore,
};
use super::cortex::{RpcCodecError, RpcHeader, RpcRequestPayload};
use super::identity::EntityId;
use super::mesh::{MeshNode, SubnetGatewayAuthorityState};
use super::subnet::SubnetExportBinding;
pub const ORG_RPC_REQUEST_DIGEST_CONTEXT: &str = "net-org-rpc-request-v1";
pub fn org_request_digest(req: &RpcRequestPayload) -> Result<[u8; 32], RpcCodecError> {
req.validate_wire_bounds()?;
let headers: Vec<RpcHeader> = req
.headers
.iter()
.filter(|(name, _)| name != ORG_ADMISSION_HEADER)
.cloned()
.collect();
let canonical = RpcRequestPayload {
service: req.service.clone(),
deadline_ns: req.deadline_ns,
flags: req.flags,
headers,
body: req.body.clone(),
};
canonical.validate_wire_bounds()?;
let mut encoded = Vec::with_capacity(canonical.encoded_len());
canonical.encode_into(&mut encoded);
Ok(blake3::derive_key(ORG_RPC_REQUEST_DIGEST_CONTEXT, &encoded))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdmissionStamp {
pub authority_ptr: usize,
pub store_ptr: usize,
pub store_generation: Option<BarrieredGeneration>,
pub poisoned: bool,
}
impl AdmissionStamp {
pub fn is_current(&self, current: &AdmissionStamp) -> bool {
self.store_generation.is_some()
&& current.store_generation.is_some()
&& self == current
&& !current.poisoned
}
}
pub fn capture_admission_stamp(mesh: &MeshNode) -> AdmissionStamp {
let authority = mesh.node_authority();
let store = mesh.org_revocation_store();
let authority_ptr = authority
.as_ref()
.map_or(0, |a| Arc::as_ptr(a) as *const () as usize);
let (store_ptr, store_generation, poisoned) = store.as_ref().map_or((0, None, false), |s| {
(
Arc::as_ptr(s) as *const () as usize,
s.barriered_generation().ok(),
s.is_poisoned(),
)
});
AdmissionStamp {
authority_ptr,
store_ptr,
store_generation,
poisoned,
}
}
pub struct ProviderFacts {
pub provider: EntityId,
pub provider_owner_org: OrgId,
pub skew_secs: u64,
pub floors: Arc<OrgRevocationState>,
pub stamp: AdmissionStamp,
_authority: Arc<NodeAuthority>,
_store: Arc<OrgRevocationStore>,
}
pub fn verify_provider_authority(
mesh: &MeshNode,
clock: &ClockSample,
) -> Result<ProviderFacts, AdmissionDenied> {
let authority = mesh
.node_authority()
.ok_or(AdmissionDenied::ProviderAuthorityUnavailable)?;
let store = mesh
.org_revocation_store()
.ok_or(AdmissionDenied::ProviderAuthorityUnavailable)?;
if store.is_poisoned() {
return Err(AdmissionDenied::ProviderAuthorityUnavailable);
}
let (floors, store_generation) = store
.snapshot_with_generation()
.map_err(|_| AdmissionDenied::ProviderAuthorityUnavailable)?;
let provider = mesh.entity_id().clone();
authority
.config
.self_verify_at(&provider, &floors, clock.wall_secs())
.map_err(|_| AdmissionDenied::ProviderAuthorityUnavailable)?;
if store.is_poisoned() {
return Err(AdmissionDenied::ProviderAuthorityUnavailable);
}
let stamp = AdmissionStamp {
authority_ptr: Arc::as_ptr(&authority) as *const () as usize,
store_ptr: Arc::as_ptr(&store) as *const () as usize,
store_generation: Some(store_generation),
poisoned: false,
};
let provider_owner_org = authority.owner_org();
let skew_secs = authority.config.verification_skew_secs;
Ok(ProviderFacts {
provider,
provider_owner_org,
skew_secs,
floors,
stamp,
_authority: authority,
_store: store,
})
}
pub struct SubnetExportFacts {
aggregate_ptr: usize,
topology_epoch: u32,
subnet_auth_epoch: u64,
authority: EntityId,
_aggregate: Arc<SubnetGatewayAuthorityState>,
}
impl SubnetExportFacts {
pub fn is_current(&self, mesh: &MeshNode) -> bool {
let live = mesh.subnet_gateway_authority();
Arc::as_ptr(&live) as *const () as usize == self.aggregate_ptr
&& mesh.subnet_topology_epoch() == self.topology_epoch
&& mesh.subnet_floor_registry().auth_epoch(&self.authority) == self.subnet_auth_epoch
}
}
pub fn verify_subnet_export(
mesh: &MeshNode,
binding: &SubnetExportBinding,
clock: &ClockSample,
) -> Result<SubnetExportFacts, AdmissionDenied> {
let state = mesh.subnet_gateway_authority();
let gateway = state
.gateway
.as_deref()
.ok_or(AdmissionDenied::ProviderAuthorityUnavailable)?;
let boundaries = state
.boundaries
.as_deref()
.ok_or(AdmissionDenied::ProviderAuthorityUnavailable)?;
let topology_epoch = mesh.subnet_topology_epoch();
let subnet_auth_epoch = mesh
.subnet_floor_registry()
.auth_epoch(&binding.subnet().authority);
gateway
.authorize_service_export(
binding,
boundaries,
topology_epoch,
subnet_auth_epoch,
clock.wall_secs(),
)
.map_err(|_| AdmissionDenied::ProviderAuthorityUnavailable)?;
Ok(SubnetExportFacts {
aggregate_ptr: Arc::as_ptr(&state) as *const () as usize,
topology_epoch,
subnet_auth_epoch,
authority: binding.subnet().authority.clone(),
_aggregate: state,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityVisibility {
Public,
OwnerScoped,
GrantedAudience,
}
pub type OrgProviderPolicy = Arc<dyn Fn(&OrgCallProof) -> bool + Send + Sync>;
#[derive(Clone)]
pub struct RegisteredRpcService {
registration_id: u64,
service: Arc<str>,
visibility: CapabilityVisibility,
admission: OrgAdmission,
provider_policy: OrgProviderPolicy,
subnet_export: Option<SubnetExportBinding>,
#[cfg(test)]
red_witness_disabled: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum RegisteredServiceError {
#[error(
"protected registration requires an org-protected admission mode, not PublicAuthenticated"
)]
PublicAdmissionNotProtected,
}
impl RegisteredRpcService {
pub fn public(registration_id: u64, service: Arc<str>) -> Self {
Self {
registration_id,
service,
visibility: CapabilityVisibility::Public,
admission: OrgAdmission::PublicAuthenticated,
provider_policy: Arc::new(|_| true),
subnet_export: None,
#[cfg(test)]
red_witness_disabled: false,
}
}
pub fn protected(
registration_id: u64,
service: Arc<str>,
admission: OrgAdmission,
provider_policy: OrgProviderPolicy,
) -> Result<Self, RegisteredServiceError> {
if matches!(admission, OrgAdmission::PublicAuthenticated) {
return Err(RegisteredServiceError::PublicAdmissionNotProtected);
}
Ok(Self {
registration_id,
service,
visibility: CapabilityVisibility::Public,
admission,
provider_policy,
subnet_export: None,
#[cfg(test)]
red_witness_disabled: false,
})
}
pub fn owner_scoped(
registration_id: u64,
service: Arc<str>,
provider_policy: OrgProviderPolicy,
) -> Self {
Self {
registration_id,
service,
visibility: CapabilityVisibility::OwnerScoped,
admission: OrgAdmission::OwnerDelegated,
provider_policy,
subnet_export: None,
#[cfg(test)]
red_witness_disabled: false,
}
}
pub fn granted(
registration_id: u64,
service: Arc<str>,
provider_policy: OrgProviderPolicy,
) -> Self {
Self {
registration_id,
service,
visibility: CapabilityVisibility::GrantedAudience,
admission: OrgAdmission::CrossOrgGranted,
provider_policy,
subnet_export: None,
#[cfg(test)]
red_witness_disabled: false,
}
}
pub fn subnet_exported(
registration_id: u64,
service: Arc<str>,
admission: OrgAdmission,
export: SubnetExportBinding,
provider_policy: OrgProviderPolicy,
) -> Result<Self, RegisteredServiceError> {
if matches!(admission, OrgAdmission::PublicAuthenticated) {
return Err(RegisteredServiceError::PublicAdmissionNotProtected);
}
Ok(Self {
registration_id,
service,
visibility: CapabilityVisibility::Public,
admission,
provider_policy,
subnet_export: Some(export),
#[cfg(test)]
red_witness_disabled: false,
})
}
pub fn subnet_export(&self) -> Option<&SubnetExportBinding> {
self.subnet_export.as_ref()
}
#[cfg(test)]
pub(crate) fn red_witness_admission_disabled(&self) -> bool {
self.red_witness_disabled
}
#[cfg(test)]
pub(crate) fn with_red_witness_disabled(mut self) -> Self {
self.red_witness_disabled = true;
self
}
pub fn registration_id(&self) -> u64 {
self.registration_id
}
pub fn service(&self) -> &Arc<str> {
&self.service
}
pub fn visibility(&self) -> CapabilityVisibility {
self.visibility
}
pub fn admission(&self) -> OrgAdmission {
self.admission
}
pub fn provider_policy(&self) -> &OrgProviderPolicy {
&self.provider_policy
}
}
impl std::fmt::Debug for RegisteredRpcService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RegisteredRpcService")
.field("registration_id", &self.registration_id)
.field("service", &self.service)
.field("visibility", &self.visibility)
.field("admission", &self.admission)
.field("provider_policy", &"<fn>")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
fn req(headers: Vec<RpcHeader>, body: &[u8]) -> RpcRequestPayload {
RpcRequestPayload {
service: "oa2-echo".to_string(),
deadline_ns: 1_700_000_000_000_000_000,
flags: 0,
headers,
body: Bytes::copy_from_slice(body),
}
}
fn h(name: &str, value: &[u8]) -> RpcHeader {
(name.to_string(), value.to_vec())
}
fn digest(req: &RpcRequestPayload) -> [u8; 32] {
org_request_digest(req).expect("well-formed fixture digests")
}
#[test]
fn digest_refuses_over_cap_requests() {
use super::super::cortex::{
MAX_RPC_BODY_LEN, MAX_RPC_HEADERS, MAX_RPC_HEADER_NAME_LEN, MAX_RPC_HEADER_VALUE_LEN,
MAX_RPC_SERVICE_NAME_LEN,
};
let mut over_service = req(vec![], b"x");
over_service.service = "s".repeat(MAX_RPC_SERVICE_NAME_LEN + 1);
assert!(matches!(
org_request_digest(&over_service),
Err(RpcCodecError::TooLarge {
field: "service",
..
})
));
let too_many = req(
(0..=MAX_RPC_HEADERS)
.map(|i| h(&format!("h{i}"), b"v"))
.collect(),
b"x",
);
assert!(matches!(
org_request_digest(&too_many),
Err(RpcCodecError::TooLarge {
field: "headers",
..
})
));
let over_name = req(
vec![h(&"n".repeat(MAX_RPC_HEADER_NAME_LEN + 1), b"v")],
b"x",
);
assert!(matches!(
org_request_digest(&over_name),
Err(RpcCodecError::TooLarge {
field: "header name",
..
})
));
let over_value = req(vec![h("k", &vec![0u8; MAX_RPC_HEADER_VALUE_LEN + 1])], b"x");
assert!(matches!(
org_request_digest(&over_value),
Err(RpcCodecError::TooLarge {
field: "header value",
..
})
));
let over_body = req(vec![], &vec![0u8; MAX_RPC_BODY_LEN + 1]);
assert!(matches!(
org_request_digest(&over_body),
Err(RpcCodecError::TooLarge { field: "body", .. })
));
assert!(org_request_digest(&req(vec![h("k", b"v")], b"body")).is_ok());
}
#[test]
fn digest_refuses_over_cap_finalized_requests() {
use super::super::cortex::{MAX_RPC_HEADERS, MAX_RPC_HEADER_VALUE_LEN};
let many_proof = req(
(0..=MAX_RPC_HEADERS)
.map(|_| h(ORG_ADMISSION_HEADER, b"p"))
.collect(),
b"x",
);
assert!(matches!(
org_request_digest(&many_proof),
Err(RpcCodecError::TooLarge {
field: "headers",
..
})
));
let big_proof = req(
vec![h(
ORG_ADMISSION_HEADER,
&vec![0u8; MAX_RPC_HEADER_VALUE_LEN + 1],
)],
b"x",
);
assert!(matches!(
org_request_digest(&big_proof),
Err(RpcCodecError::TooLarge {
field: "header value",
..
})
));
assert!(org_request_digest(&req(vec![h(ORG_ADMISSION_HEADER, b"ok")], b"body")).is_ok());
}
#[test]
fn digest_binds_header_order() {
let allow_then_deny = req(vec![h("x", b"allow"), h("x", b"deny")], b"body");
let deny_then_allow = req(vec![h("x", b"deny"), h("x", b"allow")], b"body");
assert_ne!(
digest(&allow_then_deny),
digest(&deny_then_allow),
"reversed duplicate headers must not collide",
);
let abc = req(vec![h("a", b"1"), h("b", b"2"), h("c", b"3")], b"body");
let bac = req(vec![h("b", b"2"), h("a", b"1"), h("c", b"3")], b"body");
assert_ne!(digest(&abc), digest(&bac));
}
#[test]
fn digest_ignores_admission_header_and_preserves_surrounding_order() {
let bare = req(vec![h("x", b"1"), h("y", b"2")], b"body");
let with_proof = req(
vec![
h("x", b"1"),
h(ORG_ADMISSION_HEADER, b"opaque-proof-bytes"),
h("y", b"2"),
],
b"body",
);
assert_eq!(digest(&bare), digest(&with_proof));
let with_two = req(
vec![
h(ORG_ADMISSION_HEADER, b"p1"),
h("x", b"1"),
h(ORG_ADMISSION_HEADER, b"p2"),
h("y", b"2"),
],
b"body",
);
assert_eq!(digest(&bare), digest(&with_two));
}
#[test]
fn digest_binds_header_multiplicity() {
let one = req(vec![h("x", b"1")], b"body");
let two = req(vec![h("x", b"1"), h("x", b"1")], b"body");
assert_ne!(digest(&one), digest(&two));
}
#[test]
fn digest_binds_request_fields() {
let base = req(vec![], b"body");
let base_d = digest(&base);
assert_ne!(base_d, digest(&req(vec![], b"other")));
let mut svc = req(vec![], b"body");
svc.service = "different".to_string();
assert_ne!(base_d, digest(&svc));
let mut dl = req(vec![], b"body");
dl.deadline_ns += 1;
assert_ne!(base_d, digest(&dl));
let mut fl = req(vec![], b"body");
fl.flags = 1;
assert_ne!(base_d, digest(&fl));
}
#[test]
fn registered_service_constructors_enforce_shape() {
let svc: Arc<str> = Arc::from("oa2-echo");
let pubreg = RegisteredRpcService::public(7, svc.clone());
assert_eq!(pubreg.registration_id(), 7);
assert_eq!(&**pubreg.service(), "oa2-echo");
assert_eq!(pubreg.visibility(), CapabilityVisibility::Public);
assert_eq!(pubreg.admission(), OrgAdmission::PublicAuthenticated);
for mode in [OrgAdmission::OwnerDelegated, OrgAdmission::CrossOrgGranted] {
let reg = RegisteredRpcService::protected(9, svc.clone(), mode, Arc::new(|_| true))
.expect("protected mode accepted");
assert_eq!(reg.admission(), mode);
assert_eq!(reg.visibility(), CapabilityVisibility::Public);
}
assert_eq!(
RegisteredRpcService::protected(
9,
svc.clone(),
OrgAdmission::PublicAuthenticated,
Arc::new(|_| true),
)
.err(),
Some(RegisteredServiceError::PublicAdmissionNotProtected),
);
let own = RegisteredRpcService::owner_scoped(11, svc.clone(), Arc::new(|_| true));
assert_eq!(own.visibility(), CapabilityVisibility::OwnerScoped);
assert_eq!(own.admission(), OrgAdmission::OwnerDelegated);
let granted = RegisteredRpcService::granted(13, svc.clone(), Arc::new(|_| true));
assert_eq!(granted.visibility(), CapabilityVisibility::GrantedAudience);
assert_eq!(granted.admission(), OrgAdmission::CrossOrgGranted);
}
#[test]
fn admission_stamp_currency() {
let base = AdmissionStamp {
authority_ptr: 0x1000,
store_ptr: 0x2000,
store_generation: Some(BarrieredGeneration::from_raw_for_test(7)),
poisoned: false,
};
assert!(base.is_current(&base), "identical, unpoisoned → current");
let mut gen_bumped = base;
gen_bumped.store_generation = Some(BarrieredGeneration::from_raw_for_test(8));
assert!(!base.is_current(&gen_bumped));
let mut exhausted = base;
exhausted.store_generation = None;
assert!(
!base.is_current(&exhausted),
"a live view whose generation is exhausted cannot be shown current"
);
assert!(
!exhausted.is_current(&base),
"a captured view sampled at exhaustion cannot be shown still live"
);
assert!(
!exhausted.is_current(&exhausted),
"and two exhausted samples must NOT compare equal-and-current"
);
let mut swapped = base;
swapped.authority_ptr = 0x9999;
assert!(!base.is_current(&swapped));
let mut store_swapped = base;
store_swapped.store_ptr = 0x9999;
assert!(!base.is_current(&store_swapped));
let mut poisoned = base;
poisoned.poisoned = true;
assert!(!base.is_current(&poisoned));
}
fn golden_fixture() -> RpcRequestPayload {
RpcRequestPayload {
service: "oa2-echo".to_string(),
deadline_ns: 1_700_000_000_000_000_000,
flags: 0,
headers: vec![
h("content-type", b"application/json"),
h("x-idempotency", b"k1"),
h("x-tag", b"a"),
h("x-tag", b"b"),
h(ORG_ADMISSION_HEADER, b"opaque"),
],
body: bytes::Bytes::from_static(b"hello"),
}
}
#[test]
fn digest_golden_is_literal_and_stable() {
const GOLDEN: [u8; 32] = [
0xce, 0x89, 0x3f, 0xa7, 0x73, 0x10, 0x92, 0x8e, 0x5b, 0xa7, 0x5d, 0x2b, 0xe3, 0x3a,
0x66, 0xb1, 0x8c, 0x0e, 0xae, 0x77, 0x90, 0xe1, 0xaa, 0xdf, 0x52, 0x26, 0xc7, 0x62,
0xac, 0x6f, 0x70, 0xbb,
];
let got = digest(&golden_fixture());
assert_eq!(got, GOLDEN, "wire digest drifted: {got:02x?}");
assert_ne!(got, [0u8; 32]);
let mut reversed = golden_fixture();
reversed.headers.swap(2, 3);
assert_ne!(digest(&reversed), GOLDEN);
}
}