use std::fmt;
use crate::{
EngineInvocationV1, EngineOperationV1, EnginePolicyDecisionV1, ProtocolReference,
ResolvedLocalEngineIdentityV1,
runtime_exchange::{RuntimeExchangeProfile, RuntimeRequestV1},
runtime_frame::{RuntimeFrameDirection, RuntimeFrameError, decode_runtime_frame_in},
validate_bounded_opaque_identifier,
};
pub struct RuntimeRequestSession<'key> {
key: &'key [u8; 32],
session_id: String,
engine: ResolvedLocalEngineIdentityV1,
operation: EngineOperationV1,
policy_ref: ProtocolReference,
profile: RuntimeExchangeProfile,
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> {
Self::new_in(key, session_id, admitted, RuntimeExchangeProfile::ORDINARY)
}
pub fn new_in(
key: &'key [u8; 32],
session_id: String,
admitted: &EngineInvocationV1,
profile: RuntimeExchangeProfile,
) -> 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(),
profile,
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_in(
self.key,
RuntimeFrameDirection::Request,
frame,
self.profile,
)
.map_err(RuntimeSessionError::Frame)?;
let request = RuntimeRequestV1::from_bytes_in(&payload, now_unix_ms, self.profile)
.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;