use crate::error::{CapError, CapResult};
use crate::table::CapTableEntry;
use crate::DEFAULT_MAX_DELEGATION_DEPTH;
use ruvix_types::{CapHandle, CapRights, Capability};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GrantRequest {
pub source_handle: CapHandle,
pub rights: CapRights,
pub badge: u64,
pub max_depth: u8,
}
impl GrantRequest {
#[inline]
#[must_use]
pub const fn new(source_handle: CapHandle, rights: CapRights, badge: u64) -> Self {
Self {
source_handle,
rights,
badge,
max_depth: DEFAULT_MAX_DELEGATION_DEPTH,
}
}
#[inline]
#[must_use]
pub const fn with_max_depth(mut self, max_depth: u8) -> Self {
self.max_depth = max_depth;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GrantResult {
pub capability: Capability,
pub depth: u8,
}
pub fn validate_grant(
source_entry: &CapTableEntry,
request: &GrantRequest,
) -> CapResult<GrantResult> {
if !source_entry.is_valid {
return Err(CapError::InvalidHandle);
}
let source_cap = &source_entry.capability;
let source_rights = source_cap.rights;
let has_grant = source_rights.contains(CapRights::GRANT);
let has_grant_once = source_rights.contains(CapRights::GRANT_ONCE);
if !has_grant && !has_grant_once {
return Err(CapError::CannotGrant);
}
let new_depth = source_entry.depth.saturating_add(1);
if new_depth > request.max_depth {
return Err(CapError::DelegationDepthExceeded);
}
if !request.rights.is_subset_of(source_rights) {
return Err(CapError::RightsEscalation);
}
let mut effective_rights = source_rights.intersection(request.rights);
if has_grant_once && !has_grant {
effective_rights = effective_rights
.difference(CapRights::GRANT)
.difference(CapRights::GRANT_ONCE);
}
let derived = Capability::new(
source_cap.object_id,
source_cap.object_type,
effective_rights,
request.badge,
source_cap.epoch,
);
Ok(GrantResult {
capability: derived,
depth: new_depth,
})
}
pub fn can_grant(
source_entry: &CapTableEntry,
requested_rights: CapRights,
max_depth: u8,
) -> bool {
if !source_entry.is_valid {
return false;
}
let source_rights = source_entry.capability.rights;
if !source_rights.contains(CapRights::GRANT)
&& !source_rights.contains(CapRights::GRANT_ONCE)
{
return false;
}
if source_entry.depth >= max_depth {
return false;
}
requested_rights.is_subset_of(source_rights)
}
#[cfg(test)]
mod tests {
use super::*;
use ruvix_types::{ObjectType, TaskHandle};
fn make_entry(rights: CapRights, depth: u8) -> CapTableEntry {
let cap = Capability::new(100, ObjectType::VectorStore, rights, 0, 0);
let mut entry = CapTableEntry::new_root(cap, 0, TaskHandle::new(1, 0));
entry.depth = depth;
entry
}
#[test]
fn test_validate_grant_success() {
let entry = make_entry(CapRights::ALL, 0);
let request = GrantRequest::new(
CapHandle::new(0, 0),
CapRights::READ,
42,
);
let result = validate_grant(&entry, &request).unwrap();
assert!(result.capability.rights.contains(CapRights::READ));
assert!(!result.capability.rights.contains(CapRights::WRITE));
assert_eq!(result.depth, 1);
}
#[test]
fn test_validate_grant_no_grant_right() {
let entry = make_entry(CapRights::READ | CapRights::WRITE, 0);
let request = GrantRequest::new(
CapHandle::new(0, 0),
CapRights::READ,
0,
);
assert_eq!(validate_grant(&entry, &request), Err(CapError::CannotGrant));
}
#[test]
fn test_validate_grant_rights_escalation() {
let entry = make_entry(CapRights::READ | CapRights::GRANT, 0);
let request = GrantRequest::new(
CapHandle::new(0, 0),
CapRights::READ | CapRights::WRITE,
0,
);
assert_eq!(validate_grant(&entry, &request), Err(CapError::RightsEscalation));
}
#[test]
fn test_validate_grant_depth_exceeded() {
let entry = make_entry(CapRights::ALL, 8);
let request = GrantRequest::new(
CapHandle::new(0, 0),
CapRights::READ,
0,
);
assert_eq!(
validate_grant(&entry, &request),
Err(CapError::DelegationDepthExceeded)
);
}
#[test]
fn test_validate_grant_once() {
let entry = make_entry(
CapRights::READ | CapRights::WRITE | CapRights::GRANT_ONCE,
0,
);
let request = GrantRequest::new(
CapHandle::new(0, 0),
CapRights::READ | CapRights::WRITE | CapRights::GRANT_ONCE,
0,
);
let result = validate_grant(&entry, &request).unwrap();
assert!(!result.capability.rights.contains(CapRights::GRANT));
assert!(!result.capability.rights.contains(CapRights::GRANT_ONCE));
assert!(result.capability.rights.contains(CapRights::READ));
assert!(result.capability.rights.contains(CapRights::WRITE));
}
#[test]
fn test_can_grant() {
let entry = make_entry(CapRights::READ | CapRights::GRANT, 0);
assert!(can_grant(&entry, CapRights::READ, 8));
assert!(!can_grant(&entry, CapRights::WRITE, 8)); }
}