use std::fmt;
use crate::{
EngineInvocationV1, EngineOperationV1, EnginePolicyDecisionV1, ProtocolReference,
ResolvedLocalEngineIdentityV1,
runtime_exchange::RuntimeRequestV1,
runtime_frame::{RuntimeFrameDirection, RuntimeFrameError, decode_runtime_frame},
validate_bounded_opaque_identifier,
};
pub struct RuntimeRequestSession<'key> {
key: &'key [u8; 32],
session_id: String,
engine: ResolvedLocalEngineIdentityV1,
operation: EngineOperationV1,
policy_ref: ProtocolReference,
next_sequence: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeSessionError {
InvalidScope,
Frame(RuntimeFrameError),
InvalidRequest,
ScopeMismatch,
SequenceMismatch,
SequenceExhausted,
}
impl fmt::Display for RuntimeSessionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::InvalidScope => "invalid runtime session scope",
Self::Frame(_) => "invalid authenticated runtime frame",
Self::InvalidRequest => "invalid runtime request",
Self::ScopeMismatch => "runtime request outside session scope",
Self::SequenceMismatch => "runtime request sequence mismatch",
Self::SequenceExhausted => "runtime session sequence exhausted",
})
}
}
impl std::error::Error for RuntimeSessionError {}
impl<'key> RuntimeRequestSession<'key> {
pub fn new(
key: &'key [u8; 32],
session_id: String,
admitted: &EngineInvocationV1,
) -> Result<Self, RuntimeSessionError> {
validate_bounded_opaque_identifier(&session_id, "runtime session_id")
.and_then(|()| admitted.validate())
.map_err(|_| RuntimeSessionError::InvalidScope)?;
if admitted.policy_admission.decision != EnginePolicyDecisionV1::Admitted {
return Err(RuntimeSessionError::InvalidScope);
}
Ok(Self {
key,
session_id,
engine: admitted.engine.clone(),
operation: admitted.operation.clone(),
policy_ref: admitted.policy_admission.policy_ref.clone(),
next_sequence: Some(1),
})
}
pub fn accept(
&mut self,
frame: &[u8],
now_unix_ms: u64,
) -> Result<RuntimeRequestV1, RuntimeSessionError> {
let sequence = self
.next_sequence
.ok_or(RuntimeSessionError::SequenceExhausted)?;
let payload = decode_runtime_frame(self.key, RuntimeFrameDirection::Request, frame)
.map_err(RuntimeSessionError::Frame)?;
let request = RuntimeRequestV1::from_bytes(&payload, now_unix_ms)
.map_err(|_| RuntimeSessionError::InvalidRequest)?;
if request.session_id != self.session_id
|| request.invocation.engine != self.engine
|| request.invocation.operation != self.operation
|| request.invocation.policy_admission.policy_ref != self.policy_ref
{
return Err(RuntimeSessionError::ScopeMismatch);
}
if request.sequence != sequence {
return Err(RuntimeSessionError::SequenceMismatch);
}
self.next_sequence = sequence.checked_add(1);
Ok(request)
}
}
#[cfg(test)]
mod tests;