pub struct TokenCache { /* private fields */ }Expand description
Fast permission lookup cache.
Keyed by (subject EntityId, channel_hash). Each slot holds a
list of tokens — previous versions kept a single token per
slot, which silently dropped tokens when the same subject needed
multiple distinct scopes on the same channel (e.g. one PUBLISH
token and one SUBSCRIBE token). On insert the incoming token
replaces any existing entry with an identical scope bitfield
so a refresh doesn’t stack duplicates, but tokens with different
scopes coexist.
Entries are not evicted automatically — callers should check
is_valid() on retrieved tokens, or call Self::evict_expired
on a cadence.
Capacity is bounded by MAX_TOKEN_SLOTS (slot count) and
MAX_TOKENS_PER_SLOT (tokens-with-distinct-scope per slot).
Implementations§
Source§impl TokenCache
impl TokenCache
Sourcepub fn new() -> TokenCache
pub fn new() -> TokenCache
Create an empty token cache.
Sourcepub fn insert(&self, token: PermissionToken) -> Result<(), TokenError>
pub fn insert(&self, token: PermissionToken) -> Result<(), TokenError>
Insert a token into the cache after verifying its signature.
Returns an error if the token’s signature is invalid. This prevents self-signed or tampered tokens from being cached.
Tokens with distinct scope bitfields for the same
(subject, channel_hash) are stored side-by-side.
A new token with the same scope as an existing entry
replaces the existing one — latest-issued wins so
refreshing via re-issue doesn’t leak growth.
Sourcepub fn insert_unchecked(&self, token: PermissionToken)
pub fn insert_unchecked(&self, token: PermissionToken)
Insert a token without verification (for trusted internal use).
Only use this when the token is known to be valid (e.g., just issued locally).
WILDCARD-scoped tokens are always stored under the dedicated
wildcard slot (channel_hash = 0) regardless of the token’s
own channel_hash field — that slot is where check() looks
for a cross-channel fallback. Non-wildcard tokens live in
their exact channel_hash slot.
Bounded by MAX_TOKEN_SLOTS and
MAX_TOKENS_PER_SLOT. When the slot cap is hit, novel
keys are silently dropped (existing slot keys still
refresh); when the within-slot cap is hit, novel scope
bitfields are silently dropped (existing-scope refresh
still wins). evict_expired reclaims slots as tokens
lapse, restoring admission.
Sourcepub fn check(
&self,
subject: &EntityId,
action: TokenScope,
channel_hash: u32,
) -> Result<(), TokenError>
pub fn check( &self, subject: &EntityId, action: TokenScope, channel_hash: u32, ) -> Result<(), TokenError>
Check if an entity is authorized for an action on a channel.
Returns Ok(()) if any cached token for this subject grants
action, else an error. Walks the exact-channel slot first,
then the wildcard (channel_hash = 0) slot. Within a slot,
any valid token that authorizes the requested action wins —
an expired or otherwise-invalid token in the same slot is
ignored, not blocking.
Sourcepub fn get(
&self,
subject: &EntityId,
channel_hash: u32,
) -> Option<PermissionToken>
pub fn get( &self, subject: &EntityId, channel_hash: u32, ) -> Option<PermissionToken>
Fetch any cached token for (subject, channel_hash). Exact
match only — the wildcard (channel_hash = 0) entry is a
separate key. Returns the first valid token in the slot; if
none are valid, returns any entry (so callers can still
inspect for debugging). Callers that need a specific scope
should use Self::check instead.
Sourcepub fn evict_expired(&self)
pub fn evict_expired(&self)
Remove expired tokens.
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Total number of cached tokens across all slots.
A slot is keyed by (subject, channel_hash) and can hold
multiple tokens with distinct scopes (e.g. one PUBLISH and
one SUBSCRIBE for the same peer-on-channel). An earlier
storage change from a single PermissionToken per slot to
a Vec<PermissionToken> left this method returning the
slot count instead of the token count — FFI / binding
metrics that surfaced “tokens cached” silently undercounted
whenever a slot carried more than one scope. Sum the slot
lengths so the number matches the observable cache
contents.