running_process/broker/backend_lib/
accept_handed_off.rs1use std::time::Instant;
10
11use crate::broker::server::{
12 HandoffToken, HandoffTokenError, HandoffTokenStore, HANDOFF_TOKEN_BYTES,
13};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HandedOffPayload<T> {
22 pub expected_token: HandoffToken,
24 pub presented_token: Vec<u8>,
26 pub connection: T,
28}
29
30impl<T> HandedOffPayload<T> {
31 pub fn new(
34 expected_token: HandoffToken,
35 presented_token: impl Into<Vec<u8>>,
36 connection: T,
37 ) -> Self {
38 Self {
39 expected_token,
40 presented_token: presented_token.into(),
41 connection,
42 }
43 }
44
45 pub fn presented_token(&self) -> &[u8] {
47 &self.presented_token
48 }
49
50 pub fn into_connection(self) -> T {
52 self.connection
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct AcceptedHandoff<T> {
59 pub token: HandoffToken,
61 pub connection: T,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct RejectedHandoff<T> {
68 pub payload: HandedOffPayload<T>,
71 pub reason: HandoffRejectionReason,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum HandoffAcceptance<T> {
78 Accepted(AcceptedHandoff<T>),
80 Rejected(RejectedHandoff<T>),
82}
83
84impl<T> HandoffAcceptance<T> {
85 pub fn is_accepted(&self) -> bool {
87 matches!(self, Self::Accepted(_))
88 }
89
90 pub fn is_rejected(&self) -> bool {
92 matches!(self, Self::Rejected(_))
93 }
94
95 pub fn into_result(self) -> Result<AcceptedHandoff<T>, RejectedHandoff<T>> {
97 match self {
98 Self::Accepted(accepted) => Ok(accepted),
99 Self::Rejected(rejected) => Err(rejected),
100 }
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
106pub enum HandoffRejectionReason {
107 #[error("handoff token is missing")]
109 MissingToken,
110 #[error("handoff token length was {actual_len} bytes; expected {expected_len}")]
112 InvalidTokenLength {
113 actual_len: usize,
115 expected_len: usize,
117 },
118 #[error("handoff token mismatch")]
120 TokenMismatch,
121 #[error("handoff token expired")]
123 TokenExpired,
124 #[error("handoff token is not pending")]
126 TokenNotPending,
127 #[error("handoff token store error: {error}")]
129 TokenStore {
130 error: HandoffTokenError,
132 },
133}
134
135impl From<HandoffTokenError> for HandoffRejectionReason {
136 fn from(value: HandoffTokenError) -> Self {
137 match value {
138 HandoffTokenError::TokenMismatch => Self::TokenMismatch,
139 HandoffTokenError::TokenExpired => Self::TokenExpired,
140 HandoffTokenError::TokenNotPending => Self::TokenNotPending,
141 error => Self::TokenStore { error },
142 }
143 }
144}
145
146pub fn parse_handoff_token(token: &[u8]) -> Result<HandoffToken, HandoffRejectionReason> {
148 if token.is_empty() {
149 return Err(HandoffRejectionReason::MissingToken);
150 }
151 if token.len() != HANDOFF_TOKEN_BYTES {
152 return Err(HandoffRejectionReason::InvalidTokenLength {
153 actual_len: token.len(),
154 expected_len: HANDOFF_TOKEN_BYTES,
155 });
156 }
157
158 let mut bytes = [0_u8; HANDOFF_TOKEN_BYTES];
159 bytes.copy_from_slice(token);
160 Ok(HandoffToken::from_bytes(bytes))
161}
162
163pub fn accept_handed_off<T>(
170 pending_tokens: &mut HandoffTokenStore,
171 payload: HandedOffPayload<T>,
172 now: Instant,
173) -> HandoffAcceptance<T> {
174 let presented = match parse_handoff_token(payload.presented_token()) {
175 Ok(token) => token,
176 Err(reason) => return reject(payload, reason),
177 };
178
179 match pending_tokens.consume_matching(&payload.expected_token, &presented, now) {
180 Ok(()) => HandoffAcceptance::Accepted(AcceptedHandoff {
181 token: payload.expected_token,
182 connection: payload.connection,
183 }),
184 Err(error) => reject(payload, error.into()),
185 }
186}
187
188fn reject<T>(payload: HandedOffPayload<T>, reason: HandoffRejectionReason) -> HandoffAcceptance<T> {
189 HandoffAcceptance::Rejected(RejectedHandoff { payload, reason })
190}