1use crate::protocol::RequestKind;
7use crate::session::backend::BackendResponseOrder;
8use crate::session::multiline_framing::{OrderedClientWrites, ReadyDeferredReplies};
9use crate::types::{BackendToClientBytes, ClientToBackendBytes, TransferMetrics};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum StatefulReadMode {
13 Bidirectional,
14 DrainBackendReplies,
15}
16
17pub struct SessionLoopState {
28 pub client_to_backend: ClientToBackendBytes,
30 pub backend_to_client: BackendToClientBytes,
32 pub last_reported_c2b: ClientToBackendBytes,
34 pub last_reported_b2c: BackendToClientBytes,
36 iteration_count: u32,
38 pub auth_username: Option<String>,
40 pub skip_auth_check: bool,
42 backend_replies: BackendResponseOrder,
44}
45
46impl Default for SessionLoopState {
47 fn default() -> Self {
48 Self::new(false)
49 }
50}
51
52impl SessionLoopState {
53 #[must_use]
58 pub fn new(auth_enabled: bool) -> Self {
59 Self {
60 client_to_backend: ClientToBackendBytes::zero(),
61 backend_to_client: BackendToClientBytes::zero(),
62 last_reported_c2b: ClientToBackendBytes::zero(),
63 last_reported_b2c: BackendToClientBytes::zero(),
64 iteration_count: 0,
65 auth_username: None,
66 skip_auth_check: !auth_enabled,
67 backend_replies: BackendResponseOrder::default(),
68 }
69 }
70
71 #[must_use]
76 pub fn from_initial_bytes(
77 client_to_backend: u64,
78 backend_to_client: u64,
79 auth_enabled: bool,
80 ) -> Self {
81 Self::new(auth_enabled).with_initial_bytes(client_to_backend, backend_to_client)
82 }
83
84 #[must_use]
86 pub const fn with_initial_bytes(mut self, c2b: u64, b2c: u64) -> Self {
87 self.client_to_backend = ClientToBackendBytes::new(c2b);
88 self.backend_to_client = BackendToClientBytes::new(b2c);
89 self.last_reported_c2b = self.client_to_backend;
90 self.last_reported_b2c = self.backend_to_client;
91 self
92 }
93
94 #[inline]
98 pub const fn check_and_maybe_flush_metrics(&mut self) -> bool {
99 self.iteration_count += 1;
100 if self.iteration_count >= crate::constants::session::METRICS_FLUSH_INTERVAL {
101 self.iteration_count = 0;
102 true
103 } else {
104 false
105 }
106 }
107
108 #[inline]
110 pub const fn add_client_to_backend(&mut self, bytes: usize) {
111 self.client_to_backend = self.client_to_backend.add(bytes);
112 }
113
114 #[inline]
116 pub const fn add_backend_to_client(&mut self, bytes: u64) {
117 self.backend_to_client = self.backend_to_client.add_u64(bytes);
118 }
119
120 pub fn flush_byte_deltas(
125 &mut self,
126 metrics: &crate::metrics::MetricsCollector,
127 backend_id: crate::types::BackendId,
128 username: Option<&str>,
129 ) {
130 let delta_c2b = self
131 .client_to_backend
132 .as_u64()
133 .saturating_sub(self.last_reported_c2b.as_u64());
134 let delta_b2c = self
135 .backend_to_client
136 .as_u64()
137 .saturating_sub(self.last_reported_b2c.as_u64());
138
139 if delta_c2b > 0 {
140 metrics.record_client_to_backend_bytes_for(backend_id, delta_c2b);
141 metrics.user_bytes_sent(username, delta_c2b);
142 }
143 if delta_b2c > 0 {
144 metrics.record_backend_to_client_bytes_for(backend_id, delta_b2c);
145 metrics.user_bytes_received(username, delta_b2c);
146 }
147
148 self.last_reported_c2b = self.client_to_backend;
149 self.last_reported_b2c = self.backend_to_client;
150 }
151
152 #[must_use]
154 pub fn into_metrics(self) -> TransferMetrics {
155 TransferMetrics {
156 client_to_backend: self.client_to_backend,
157 backend_to_client: self.backend_to_client,
158 }
159 }
160
161 #[inline]
163 pub fn mark_authenticated(&mut self) {
164 self.skip_auth_check = true;
165 }
166
167 pub fn apply_auth_result(&mut self, result: &super::common::AuthHandlerResult) -> u64 {
171 let bytes = result.bytes_written();
172 self.add_backend_to_client(bytes);
173 if result.should_skip_further_checks() {
174 self.mark_authenticated();
175 }
176 bytes
177 }
178
179 #[inline]
181 pub fn mark_backend_request_sent(&mut self, kind: RequestKind) {
182 self.backend_replies.push_request(kind);
183 }
184
185 #[inline]
187 #[must_use]
188 pub fn has_pending_backend_replies(&self) -> bool {
189 self.backend_replies.has_pending_backend_replies()
190 }
191
192 #[must_use]
197 pub(in crate::session) fn client_writes_for_backend_read<'a>(
198 &mut self,
199 backend_read: &'a [u8],
200 ) -> OrderedClientWrites<'a> {
201 self.backend_replies
202 .client_writes_for_backend_read(backend_read)
203 }
204
205 pub fn push_deferred_reply(&mut self, reply: &'static [u8]) {
207 self.backend_replies.push_deferred_reply(reply);
208 }
209
210 #[inline]
212 #[must_use]
213 pub fn has_deferred_replies(&self) -> bool {
214 self.backend_replies.has_deferred_replies()
215 }
216
217 #[must_use]
219 pub fn read_mode(&self) -> StatefulReadMode {
220 if self.backend_replies.should_drain_backend_replies() {
221 StatefulReadMode::DrainBackendReplies
222 } else {
223 StatefulReadMode::Bidirectional
224 }
225 }
226
227 #[must_use]
229 pub fn take_ready_deferred_replies(&mut self) -> ReadyDeferredReplies {
230 self.backend_replies.take_ready_deferred_replies()
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::session::common::AuthHandlerResult;
238 use std::borrow::Cow;
239
240 fn drain_backend_bytes(state: &mut SessionLoopState, bytes: &[u8]) -> Vec<Vec<u8>> {
241 state
242 .client_writes_for_backend_read(bytes)
243 .into_iter()
244 .map(Cow::into_owned)
245 .collect()
246 }
247
248 #[test]
249 fn test_session_loop_state_new() {
250 let state = SessionLoopState::new(true);
251 assert_eq!(state.client_to_backend.as_u64(), 0);
252 assert_eq!(state.backend_to_client.as_u64(), 0);
253 assert!(!state.skip_auth_check); assert!(state.auth_username.is_none());
255
256 let state2 = SessionLoopState::new(false);
257 assert!(state2.skip_auth_check); }
259
260 #[test]
261 fn test_session_loop_state_default() {
262 let state = SessionLoopState::default();
263 assert_eq!(state.client_to_backend.as_u64(), 0);
264 assert!(state.skip_auth_check); assert!(!state.has_pending_backend_replies());
266 }
267
268 #[test]
269 fn test_session_loop_state_builder_pattern() {
270 let state = SessionLoopState::new(false).with_initial_bytes(1000, 500);
271
272 assert_eq!(state.client_to_backend.as_u64(), 1000);
273 assert_eq!(state.backend_to_client.as_u64(), 500);
274 }
275
276 #[test]
277 fn test_session_loop_state_from_initial_bytes() {
278 let state = SessionLoopState::from_initial_bytes(100, 200, true);
279 assert_eq!(state.client_to_backend.as_u64(), 100);
280 assert_eq!(state.backend_to_client.as_u64(), 200);
281 assert_eq!(state.last_reported_c2b.as_u64(), 100);
282 assert_eq!(state.last_reported_b2c.as_u64(), 200);
283 assert!(!state.skip_auth_check);
284 }
285
286 #[test]
287 fn test_session_loop_state_add_bytes() {
288 let mut state = SessionLoopState::new(false);
289
290 state.add_client_to_backend(100);
291 assert_eq!(state.client_to_backend.as_u64(), 100);
292
293 state.add_backend_to_client(200);
294 assert_eq!(state.backend_to_client.as_u64(), 200);
295
296 state.add_client_to_backend(50);
298 state.add_backend_to_client(50);
299 assert_eq!(state.client_to_backend.as_u64(), 150);
300 assert_eq!(state.backend_to_client.as_u64(), 250);
301 }
302
303 #[test]
304 fn test_session_loop_state_mark_authenticated() {
305 let mut state = SessionLoopState::new(true);
306 assert!(!state.skip_auth_check);
307
308 state.mark_authenticated();
309 assert!(state.skip_auth_check);
310 }
311
312 #[test]
313 fn test_session_loop_state_deferred_replies() {
314 let mut state = SessionLoopState::new(false);
315
316 state.mark_backend_request_sent(RequestKind::Date);
317 assert!(state.has_pending_backend_replies());
318
319 state.push_deferred_reply(b"205 Goodbye\r\n");
320 assert!(state.take_ready_deferred_replies().is_empty());
321
322 let rendered = drain_backend_bytes(&mut state, b"111 20260505120000\r\n");
323 assert!(!state.has_pending_backend_replies());
324 assert_eq!(
325 rendered,
326 vec![
327 b"111 20260505120000\r\n".to_vec(),
328 b"205 Goodbye\r\n".to_vec()
329 ]
330 );
331 }
332
333 #[test]
334 fn test_session_loop_state_ready_deferred_replies_stay_inline() {
335 let mut state = SessionLoopState::new(false);
336
337 state.push_deferred_reply(b"205 Goodbye\r\n");
338 let replies = state.take_ready_deferred_replies();
339
340 assert!(
341 !replies.spilled(),
342 "ready deferred replies should not allocate in the common case"
343 );
344 assert_eq!(replies.as_slice(), [b"205 Goodbye\r\n".as_slice()]);
345 }
346
347 #[test]
348 fn test_session_loop_state_enters_backend_drain_mode_for_deferred_locals() {
349 let mut state = SessionLoopState::new(false);
350
351 assert_eq!(state.read_mode(), StatefulReadMode::Bidirectional);
352
353 state.mark_backend_request_sent(RequestKind::Help);
354 state.push_deferred_reply(b"101 Capability list:\r\n.\r\n");
355 assert_eq!(state.read_mode(), StatefulReadMode::DrainBackendReplies);
356
357 drain_backend_bytes(&mut state, b"100 Help follows\r\n.\r\n");
358 assert_eq!(state.read_mode(), StatefulReadMode::Bidirectional);
359 }
360
361 #[test]
362 fn test_pending_backend_replies_handle_empty_response_body() {
363 let mut state = SessionLoopState::new(false);
364
365 state.mark_backend_request_sent(RequestKind::Help);
366 drain_backend_bytes(&mut state, b"100 Help follows\r\n.\r\n");
367 assert!(
368 !state.has_pending_backend_replies(),
369 "empty response body replies should complete immediately"
370 );
371 }
372
373 #[test]
374 fn test_pending_backend_reply_tracking_cap_prevents_unbounded_growth() {
375 let mut state = SessionLoopState::new(false);
376 state.mark_backend_request_sent(RequestKind::Date);
377
378 drain_backend_bytes(
379 &mut state,
380 &vec![b'x'; crate::constants::buffer::COMMAND + 1],
381 );
382
383 assert!(
384 !state.has_pending_backend_replies(),
385 "oversized replies should stop pending bookkeeping instead of growing forever"
386 );
387 }
388
389 #[test]
390 fn test_deferred_local_reply_flushes_before_later_backend_reply() {
391 let mut state = SessionLoopState::new(false);
392 let deferred = b"101 Capability list:\r\n.\r\n";
393
394 state.mark_backend_request_sent(RequestKind::Date);
395 state.push_deferred_reply(deferred);
396 state.mark_backend_request_sent(RequestKind::Date);
397
398 let rendered = drain_backend_bytes(&mut state, b"111 20260505120000\r\n");
399 assert!(
400 state.has_pending_backend_replies(),
401 "later backend replies must remain pending after the first one completes"
402 );
403 assert_eq!(state.read_mode(), StatefulReadMode::Bidirectional);
404 assert_eq!(
405 rendered,
406 vec![b"111 20260505120000\r\n".to_vec(), deferred.to_vec()]
407 );
408
409 drain_backend_bytes(&mut state, b"111 20260505120001\r\n");
410 assert!(!state.has_pending_backend_replies());
411 }
412
413 #[test]
414 fn test_client_writes_for_backend_read_orders_replies_around_deferred_reply() {
415 let mut state = SessionLoopState::new(false);
416 let deferred = b"101 Capability list:\r\n.\r\n";
417
418 state.mark_backend_request_sent(RequestKind::Date);
419 state.push_deferred_reply(deferred);
420 state.mark_backend_request_sent(RequestKind::Date);
421
422 let rendered: Vec<Vec<u8>> = state
423 .client_writes_for_backend_read(b"111 20260505120000\r\n111 20260505120001\r\n")
424 .into_iter()
425 .map(Cow::into_owned)
426 .collect();
427
428 assert_eq!(
429 rendered,
430 vec![
431 b"111 20260505120000\r\n".to_vec(),
432 deferred.to_vec(),
433 b"111 20260505120001\r\n".to_vec(),
434 ]
435 );
436 assert!(!state.has_pending_backend_replies());
437 }
438
439 #[test]
440 fn test_client_writes_for_backend_read_orders_pipelined_body_replies() {
441 let mut state = SessionLoopState::new(false);
442 state.mark_backend_request_sent(RequestKind::Help);
443 state.mark_backend_request_sent(RequestKind::Help);
444
445 let rendered: Vec<Vec<u8>> = state
446 .client_writes_for_backend_read(
447 b"100 Help follows\r\nbody one\r\n.\r\n100 Help follows\r\nbody two\r\n.\r\n",
448 )
449 .into_iter()
450 .map(Cow::into_owned)
451 .collect();
452
453 assert_eq!(
454 rendered,
455 vec![
456 b"100 Help follows\r\nbody one\r\n.\r\n".to_vec(),
457 b"100 Help follows\r\nbody two\r\n.\r\n".to_vec(),
458 ]
459 );
460 assert!(!state.has_pending_backend_replies());
461 }
462
463 #[test]
464 fn test_session_loop_state_apply_auth_result() {
465 let mut state = SessionLoopState::new(true);
466 assert!(!state.skip_auth_check);
467 assert_eq!(state.backend_to_client.as_u64(), 0);
468
469 let result = AuthHandlerResult::Authenticated {
471 bytes_written: 100,
472 skip_further_checks: true,
473 };
474 let bytes = state.apply_auth_result(&result);
475
476 assert_eq!(bytes, 100);
477 assert_eq!(state.backend_to_client.as_u64(), 100);
478 assert!(state.skip_auth_check);
479 }
480
481 #[test]
482 fn test_session_loop_state_apply_auth_result_not_authenticated() {
483 let mut state = SessionLoopState::new(true);
484
485 let result = AuthHandlerResult::NotAuthenticated { bytes_written: 50 };
486 state.apply_auth_result(&result);
487
488 assert_eq!(state.backend_to_client.as_u64(), 50);
489 assert!(!state.skip_auth_check); }
491
492 #[test]
493 fn test_session_loop_state_into_metrics() {
494 let state = SessionLoopState::new(false).with_initial_bytes(1000, 2000);
495
496 let metrics = state.into_metrics();
497 assert_eq!(metrics.client_to_backend.as_u64(), 1000);
498 assert_eq!(metrics.backend_to_client.as_u64(), 2000);
499 }
500
501 #[test]
502 fn test_session_loop_state_metrics_flush_interval() {
503 use crate::constants::session::METRICS_FLUSH_INTERVAL;
504
505 let mut state = SessionLoopState::new(false);
506
507 for _ in 0..(METRICS_FLUSH_INTERVAL - 1) {
509 assert!(!state.check_and_maybe_flush_metrics());
510 }
511
512 assert!(state.check_and_maybe_flush_metrics());
514
515 assert!(!state.check_and_maybe_flush_metrics());
517 }
518}