lean_ctx_protocol/
runtime_session.rs1use std::fmt;
7
8use crate::{
9 EngineInvocationV1, EngineOperationV1, EnginePolicyDecisionV1, ProtocolReference,
10 ResolvedLocalEngineIdentityV1,
11 runtime_exchange::RuntimeRequestV1,
12 runtime_frame::{RuntimeFrameDirection, RuntimeFrameError, decode_runtime_frame},
13 validate_bounded_opaque_identifier,
14};
15
16pub struct RuntimeRequestSession<'key> {
19 key: &'key [u8; 32],
20 session_id: String,
21 engine: ResolvedLocalEngineIdentityV1,
22 operation: EngineOperationV1,
23 policy_ref: ProtocolReference,
24 next_sequence: Option<u64>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum RuntimeSessionError {
29 InvalidScope,
30 Frame(RuntimeFrameError),
31 InvalidRequest,
32 ScopeMismatch,
33 SequenceMismatch,
34 SequenceExhausted,
35}
36
37impl fmt::Display for RuntimeSessionError {
38 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39 formatter.write_str(match self {
40 Self::InvalidScope => "invalid runtime session scope",
41 Self::Frame(_) => "invalid authenticated runtime frame",
42 Self::InvalidRequest => "invalid runtime request",
43 Self::ScopeMismatch => "runtime request outside session scope",
44 Self::SequenceMismatch => "runtime request sequence mismatch",
45 Self::SequenceExhausted => "runtime session sequence exhausted",
46 })
47 }
48}
49
50impl std::error::Error for RuntimeSessionError {}
51
52impl<'key> RuntimeRequestSession<'key> {
53 pub fn new(
57 key: &'key [u8; 32],
58 session_id: String,
59 admitted: &EngineInvocationV1,
60 ) -> Result<Self, RuntimeSessionError> {
61 validate_bounded_opaque_identifier(&session_id, "runtime session_id")
62 .and_then(|()| admitted.validate())
63 .map_err(|_| RuntimeSessionError::InvalidScope)?;
64 if admitted.policy_admission.decision != EnginePolicyDecisionV1::Admitted {
65 return Err(RuntimeSessionError::InvalidScope);
66 }
67 Ok(Self {
68 key,
69 session_id,
70 engine: admitted.engine.clone(),
71 operation: admitted.operation.clone(),
72 policy_ref: admitted.policy_admission.policy_ref.clone(),
73 next_sequence: Some(1),
74 })
75 }
76
77 pub fn accept(
82 &mut self,
83 frame: &[u8],
84 now_unix_ms: u64,
85 ) -> Result<RuntimeRequestV1, RuntimeSessionError> {
86 let sequence = self
87 .next_sequence
88 .ok_or(RuntimeSessionError::SequenceExhausted)?;
89 let payload = decode_runtime_frame(self.key, RuntimeFrameDirection::Request, frame)
90 .map_err(RuntimeSessionError::Frame)?;
91 let request = RuntimeRequestV1::from_bytes(&payload, now_unix_ms)
92 .map_err(|_| RuntimeSessionError::InvalidRequest)?;
93 if request.session_id != self.session_id
94 || request.invocation.engine != self.engine
95 || request.invocation.operation != self.operation
96 || request.invocation.policy_admission.policy_ref != self.policy_ref
97 {
98 return Err(RuntimeSessionError::ScopeMismatch);
99 }
100 if request.sequence != sequence {
101 return Err(RuntimeSessionError::SequenceMismatch);
102 }
103 self.next_sequence = sequence.checked_add(1);
104 Ok(request)
105 }
106}
107
108#[cfg(test)]
109mod tests;