///|
/// Mutable counters paired with one immutable compiled grant.
struct GrantState {
grant : Grant
mut remaining_calls : Int?
mut remaining_bytes : Int64?
}
///|
pub fn runtime(permit : Permit) -> Runtime {
let states : Array[GrantState] = []
for grant in permit.grants {
states.push({
grant,
remaining_calls: grant.budget.max_calls,
remaining_bytes: grant.budget.max_bytes,
})
}
{
permit_id: permit.id,
states,
seen_invocations: Set([]),
receipts: [],
sequence: 0,
}
}
///|
fn Runtime::emit(
self : Runtime,
invocation_id : String,
requested : EffectScope,
logical_time : Int64,
byte_cost : Int64,
verdict : Verdict,
grant_id : String?,
reason : ReasonCode,
message : String,
remaining_calls : Int?,
remaining_bytes : Int64?,
) -> Receipt {
self.sequence = self.sequence + 1
let receipt = {
sequence: self.sequence,
invocation_id,
permit_id: self.permit_id,
verdict,
grant_id,
requested,
effect: requested.canonical(),
logical_time,
byte_cost,
reason,
message,
remaining_calls,
remaining_bytes,
}
self.receipts.push(receipt)
receipt
}
///|
fn reason_message(reason : ReasonCode) -> String {
match reason {
Granted => "effect is covered and its budget was consumed"
NoMatchingGrant => "no grant contains the requested effect"
PermitExpired =>
"a matching grant is expired and no usable alternative exists"
CallBudgetExhausted =>
"a matching grant has no calls remaining and no usable alternative exists"
ByteBudgetExhausted =>
"a matching grant has insufficient bytes and no usable alternative exists"
DuplicateInvocation => "invocation identifier has already been checked"
}
}
///|
fn record_proof_check(
collect_proof : Bool,
checks : Array[ProofCheck],
kind : ProofCheckKind,
status : ProofCheckStatus,
grant_id : String?,
explanation : String,
) -> Unit {
if collect_proof {
checks.push({ kind, status, grant_id, explanation, })
}
}
///|
fn checked_authorization(
receipt : Receipt,
checks : Array[ProofCheck],
) -> CheckedAuthorization {
{
receipt,
proof: {
sequence: receipt.sequence,
invocation_id: receipt.invocation_id,
permit_id: receipt.permit_id,
requested: receipt.requested,
effect: receipt.effect,
verdict: receipt.verdict,
decision_grant_id: receipt.grant_id,
reason: receipt.reason,
checks,
},
}
}
///|
fn Runtime::check_decision(
self : Runtime,
invocation_id : String,
requested : EffectScope,
now : Int64,
collect_proof : Bool,
byte_cost? : Int64 = 0L,
) -> (Receipt, Array[ProofCheck]) raise PermitError {
let id = invocation_id.trim().to_owned()
if id is "" {
raise PermitError::EmptyInvocationId
}
if now < 0L {
raise PermitError::InvalidLogicalTime(now)
}
if byte_cost < 0L {
raise PermitError::InvalidByteCost(byte_cost)
}
let checks : Array[ProofCheck] = []
if !self.seen_invocations.add_and_check(id) {
record_proof_check(
collect_proof,
checks,
ProofCheckKind::InvocationUnique,
ProofCheckStatus::Fail,
None,
"invocation identifier was already checked",
)
let receipt = self.emit(
id,
requested,
now,
byte_cost,
Verdict::Deny,
None,
ReasonCode::DuplicateInvocation,
reason_message(DuplicateInvocation),
None,
None,
)
return (receipt, checks)
}
record_proof_check(
collect_proof,
checks,
ProofCheckKind::InvocationUnique,
ProofCheckStatus::Pass,
None,
"invocation identifier is fresh",
)
let mut matched = false
let mut denial_reason = ReasonCode::NoMatchingGrant
let mut denial_grant : String? = None
let mut denial_calls : Int? = None
let mut denial_bytes : Int64? = None
let mut candidate : Int? = None
for index, state in self.states {
let grant_id = Some(state.grant.id)
if !(effect_contains(state.grant.effect, requested)
is Containment::Contained) {
record_proof_check(
collect_proof,
checks,
ProofCheckKind::ScopeContained,
ProofCheckStatus::Fail,
grant_id,
"requested effect is outside this grant",
)
for
kind in [
ProofCheckKind::NotExpired,
ProofCheckKind::CallBudgetAvailable,
ProofCheckKind::ByteBudgetAvailable,
] {
record_proof_check(
collect_proof,
checks,
kind,
ProofCheckStatus::Skipped,
grant_id,
"not evaluated because scope containment failed",
)
}
continue
}
record_proof_check(
collect_proof,
checks,
ProofCheckKind::ScopeContained,
ProofCheckStatus::Pass,
grant_id,
"requested effect is contained by this grant",
)
let expired = match state.grant.budget.expires_at {
Some(expiry) => now >= expiry
None => false
}
let calls_exhausted = match state.remaining_calls {
Some(remaining) => remaining <= 0
None => false
}
let bytes_exhausted = match state.remaining_bytes {
Some(remaining) => byte_cost > remaining
None => false
}
record_proof_check(
collect_proof,
checks,
ProofCheckKind::NotExpired,
if expired {
ProofCheckStatus::Fail
} else {
ProofCheckStatus::Pass
},
grant_id,
if expired {
"grant is expired at the supplied logical time"
} else {
"grant is active at the supplied logical time"
},
)
record_proof_check(
collect_proof,
checks,
ProofCheckKind::CallBudgetAvailable,
if calls_exhausted {
ProofCheckStatus::Fail
} else {
ProofCheckStatus::Pass
},
grant_id,
if calls_exhausted {
"grant has no calls remaining"
} else {
"grant has call capacity"
},
)
record_proof_check(
collect_proof,
checks,
ProofCheckKind::ByteBudgetAvailable,
if bytes_exhausted {
ProofCheckStatus::Fail
} else {
ProofCheckStatus::Pass
},
grant_id,
if bytes_exhausted {
"grant has insufficient bytes"
} else {
"grant has byte capacity for this request"
},
)
if expired || calls_exhausted || bytes_exhausted {
if !matched {
matched = true
denial_reason = if expired {
ReasonCode::PermitExpired
} else if calls_exhausted {
ReasonCode::CallBudgetExhausted
} else {
ReasonCode::ByteBudgetExhausted
}
denial_grant = grant_id
denial_calls = state.remaining_calls
denial_bytes = state.remaining_bytes
}
continue
}
match candidate {
None => candidate = Some(index)
Some(current) =>
if candidate_is_tighter(state, self.states[current]) {
candidate = Some(index)
}
}
if state.grant.effect == requested {
break
}
}
match candidate {
Some(index) => {
let state = self.states[index]
match state.remaining_calls {
Some(remaining) => state.remaining_calls = Some(remaining - 1)
None => ()
}
match state.remaining_bytes {
Some(remaining) => state.remaining_bytes = Some(remaining - byte_cost)
None => ()
}
let receipt = self.emit(
id,
requested,
now,
byte_cost,
Verdict::Allow,
Some(state.grant.id),
ReasonCode::Granted,
reason_message(Granted),
state.remaining_calls,
state.remaining_bytes,
)
return (receipt, checks)
}
None => ()
}
let receipt = self.emit(
id,
requested,
now,
byte_cost,
Verdict::Deny,
denial_grant,
denial_reason,
reason_message(denial_reason),
denial_calls,
denial_bytes,
)
(receipt, checks)
}
///|
pub fn Runtime::check_with_proof(
self : Runtime,
invocation_id : String,
requested : EffectScope,
now : Int64,
byte_cost? : Int64 = 0L,
) -> CheckedAuthorization raise PermitError {
let (receipt, checks) = self.check_decision(
invocation_id,
requested,
now,
true,
byte_cost~,
)
checked_authorization(receipt, checks)
}
///|
pub fn Runtime::check(
self : Runtime,
invocation_id : String,
requested : EffectScope,
now : Int64,
byte_cost? : Int64 = 0L,
) -> Receipt raise PermitError {
let (receipt, _) = self.check_decision(
invocation_id,
requested,
now,
false,
byte_cost~,
)
receipt
}
///|
pub fn Runtime::permit_id(self : Runtime) -> String {
self.permit_id
}
///|
pub fn Runtime::receipt_count(self : Runtime) -> Int {
self.receipts.length()
}
///|
pub fn Runtime::receipts(self : Runtime) -> Array[Receipt] {
self.receipts.copy()
}
///|
pub fn Runtime::remaining_budget(self : Runtime, grant_id : String) -> Budget? {
for state in self.states {
if state.grant.id == grant_id {
return Some({
max_calls: state.remaining_calls,
max_bytes: state.remaining_bytes,
expires_at: state.grant.budget.expires_at,
})
}
}
None
}
///|
pub fn Receipt::allowed(self : Receipt) -> Bool {
self.verdict is Verdict::Allow
}