1use std::{
2 any::Any,
3 cell::{Cell, RefCell},
4 collections::{HashMap, hash_map::Entry},
5 future::Future,
6 net::SocketAddr,
7 panic::AssertUnwindSafe,
8 rc::Rc,
9 sync::Mutex,
10 time::Duration,
11};
12
13use easyfix_messages::{
14 fields::{FixString, SessionStatus},
15 messages::{FixtMessage, Message},
16};
17use futures_util::{FutureExt, Stream, pin_mut};
18use tokio::{
19 self,
20 io::{AsyncRead, AsyncWrite, AsyncWriteExt},
21 net::TcpStream,
22 sync::{mpsc, oneshot},
23};
24use tokio_stream::StreamExt;
25use tracing::{Instrument, Span, debug, error, info, info_span, warn};
26
27use crate::{
28 DisconnectReason, Error, NO_INBOUND_TIMEOUT_PADDING, Sender, SessionError,
29 TEST_REQUEST_THRESHOLD,
30 acceptor::{ActiveSessionsMap, SessionsMap},
31 application::{Emitter, FixEventInternal},
32 messages_storage::MessagesStorage,
33 session::Session,
34 session_id::SessionId,
35 session_state::State,
36 settings::{SessionSettings, Settings},
37};
38
39mod input_stream;
40pub use input_stream::{InputEvent, InputStream, input_stream};
41
42mod output_stream;
43use output_stream::{OutputEvent, output_stream};
44
45pub mod time;
46use time::{timeout, timeout_at, timeout_stream};
47
48static SENDERS: Mutex<Option<HashMap<SessionId, Sender>>> = Mutex::new(None);
49
50pub fn register_sender(session_id: SessionId, sender: Sender) {
51 if let Entry::Vacant(entry) = SENDERS
52 .lock()
53 .unwrap()
54 .get_or_insert_with(HashMap::new)
55 .entry(session_id)
56 {
57 entry.insert(sender);
58 }
59}
60
61pub fn unregister_sender(session_id: &SessionId) {
62 if SENDERS
63 .lock()
64 .unwrap()
65 .get_or_insert_with(HashMap::new)
66 .remove(session_id)
67 .is_none()
68 {
69 }
71}
72
73pub fn sender(session_id: &SessionId) -> Option<Sender> {
74 SENDERS
75 .lock()
76 .unwrap()
77 .get_or_insert_with(HashMap::new)
78 .get(session_id)
79 .cloned()
80}
81
82pub fn send(session_id: &SessionId, msg: Box<Message>) -> Result<(), Box<Message>> {
84 if let Some(sender) = sender(session_id) {
85 sender.send(msg).map_err(|msg| msg.body)
86 } else {
87 Err(msg)
88 }
89}
90
91pub fn send_raw(msg: Box<FixtMessage>) -> Result<(), Box<FixtMessage>> {
92 if let Some(sender) = sender(&SessionId::from_input_msg(&msg)) {
93 sender.send_raw(msg)
94 } else {
95 Err(msg)
96 }
97}
98
99async fn first_msg(
100 stream: &mut (impl Stream<Item = InputEvent> + Unpin),
101 logon_timeout: Duration,
102) -> Result<Box<FixtMessage>, Error> {
103 match timeout(logon_timeout, stream.next()).await {
104 Ok(Some(InputEvent::Message(msg))) => Ok(msg),
105 Ok(Some(InputEvent::IoError(error))) => Err(error.into()),
106 Ok(Some(InputEvent::DeserializeError(error))) => {
107 error!("failed to deserialize first message: {error}");
108 Err(Error::SessionError(SessionError::LogonNeverReceived))
109 }
110 _ => Err(Error::SessionError(SessionError::LogonNeverReceived)),
111 }
112}
113
114#[derive(Debug)]
115struct Connection<S> {
116 session: Rc<Session<S>>,
117}
118
119pub(crate) type PendingLogout = Rc<RefCell<Option<(SessionId, DisconnectReason)>>>;
123
124fn panic_message(panic: &(dyn Any + Send)) -> &str {
125 if let Some(message) = panic.downcast_ref::<&str>() {
126 message
127 } else if let Some(message) = panic.downcast_ref::<String>() {
128 message
129 } else {
130 "<non-string panic payload>"
131 }
132}
133
134pub(crate) async fn supervise_connection(
142 connection: impl Future<Output = ()>,
143 pending_logout: PendingLogout,
144 emitter: &Emitter,
145) {
146 if let Err(panic) = AssertUnwindSafe(connection).catch_unwind().await {
151 error!(
152 "connection task panicked: {}",
153 panic_message(panic.as_ref())
154 );
155 let parked_logout = pending_logout.borrow_mut().take();
156 if let Some((session_id, reason)) = parked_logout {
157 emitter
158 .send(FixEventInternal::Logout(session_id, reason))
159 .await;
160 }
161 }
162}
163
164struct SessionCleanupGuard<S: MessagesStorage> {
170 session_id: SessionId,
171 state: Rc<RefCell<State<S>>>,
172 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
173 pending_logout: PendingLogout,
174 reset_on_disconnect: bool,
175}
176
177impl<S: MessagesStorage> Drop for SessionCleanupGuard<S> {
178 fn drop(&mut self) {
179 unregister_sender(&self.session_id);
180
181 match self.active_sessions.try_borrow_mut() {
183 Ok(mut active_sessions) => {
184 active_sessions.remove(&self.session_id);
185 }
186 Err(_) => error!(
187 session_id = %self.session_id,
188 "session cleanup failed: active sessions map already borrowed"
189 ),
190 }
191
192 match self.state.try_borrow_mut() {
193 Ok(mut state) => {
194 let logout_not_emitted = state.logon_received() || state.logon_sent();
200 state.set_logon_received(false);
201 state.set_logon_sent(false);
202 if !state.disconnected() {
203 warn!(
204 session_id = %self.session_id,
205 "connection task finished without disconnecting, forcing disconnected state"
206 );
207 state.disconnect(self.reset_on_disconnect);
208 }
209 if logout_not_emitted {
210 *self.pending_logout.borrow_mut() =
213 Some((self.session_id.clone(), DisconnectReason::Disconnected));
214 }
215 }
216 Err(_) => error!(
217 session_id = %self.session_id,
218 "session cleanup failed: session state already borrowed"
219 ),
220 }
221 }
222}
223
224#[expect(clippy::too_many_arguments)]
225pub(crate) async fn acceptor_connection<S>(
226 reader: impl AsyncRead + Unpin,
227 writer: impl AsyncWrite + Unpin,
228 settings: Settings,
229 sessions: Rc<RefCell<SessionsMap<S>>>,
230 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
231 emitter: Emitter,
232 enabled: Rc<Cell<bool>>,
233 pending_logout: PendingLogout,
234 peer_addr: SocketAddr,
235) where
236 S: MessagesStorage,
237{
238 let stream = input_stream(reader);
239 let logon_timeout =
240 settings.auto_disconnect_after_no_logon_received + NO_INBOUND_TIMEOUT_PADDING;
241 pin_mut!(stream);
242 let msg = match first_msg(&mut stream, logon_timeout).await {
243 Ok(msg) => msg,
244 Err(err) => {
245 error!(%err, "failed to establish new session");
246 return;
247 }
248 };
249
250 let session_id = SessionId::from_input_msg(&msg);
251 debug!(first_msg = ?msg);
252
253 if !enabled.get() {
255 warn!("Acceptor is disabled, drop connection");
256 return;
257 }
258
259 let (sender, receiver) = mpsc::unbounded_channel();
260 let sender = Sender::new(sender);
261
262 let Some((session_settings, session_state)) = sessions.borrow().get_session(&session_id) else {
263 error!(%session_id, "failed to establish new session: unknown session id");
264 return;
265 };
266 if !session_state.borrow_mut().disconnected()
267 || active_sessions.borrow().contains_key(&session_id)
268 {
269 error!(%session_id, "Session already active");
270 return;
271 }
272 session_state.borrow_mut().set_disconnected(false);
273 register_sender(session_id.clone(), sender.clone());
274
275 let _cleanup_guard = SessionCleanupGuard {
276 session_id: session_id.clone(),
277 state: session_state.clone(),
278 active_sessions: active_sessions.clone(),
279 pending_logout,
280 reset_on_disconnect: session_settings.reset_on_disconnect,
281 };
282
283 let (disconnect_tx, disconnect_rx) = oneshot::channel();
284
285 let session = Rc::new(Session::new(
286 settings,
287 session_settings,
288 session_state,
289 sender,
290 emitter.clone(),
291 disconnect_tx,
292 peer_addr,
293 ));
294
295 active_sessions
296 .borrow_mut()
297 .insert(session_id.clone(), session.clone());
298
299 let session_span = info_span!(
300 parent: None,
301 "session",
302 id = %session_id
303 );
304 session_span.follows_from(Span::current());
305
306 let input_loop_span = info_span!(parent: &session_span, "in");
307 let output_loop_span = info_span!(parent: &session_span, "out");
308
309 let force_disconnection_with_reason = session
310 .on_message_in(msg)
311 .instrument(input_loop_span.clone())
312 .await;
313
314 emitter
316 .send(FixEventInternal::Created(session_id.clone()))
317 .await;
318
319 let input_timeout_duration = session.heartbeat_interval().mul_f32(TEST_REQUEST_THRESHOLD);
320 let input_stream = timeout_stream(input_timeout_duration, stream)
321 .map(|res| res.unwrap_or(InputEvent::Timeout));
322 pin_mut!(input_stream);
323
324 let output_stream = output_stream(session.clone(), session.heartbeat_interval(), receiver);
325 pin_mut!(output_stream);
326
327 let connection = Connection::new(session);
328 let (input_closed_tx, input_closed_rx) = oneshot::channel();
329
330 tokio::join!(
331 connection
332 .input_loop(
333 input_stream,
334 input_closed_tx,
335 force_disconnection_with_reason,
336 disconnect_rx,
337 )
338 .instrument(input_loop_span),
339 connection
340 .output_loop(writer, output_stream, input_closed_rx)
341 .instrument(output_loop_span),
342 );
343 session_span.in_scope(|| {
344 info!("connection closed");
345 });
346}
347
348#[expect(clippy::too_many_arguments)]
349pub(crate) async fn initiator_connection<S>(
350 tcp_stream: TcpStream,
351 settings: Settings,
352 session_settings: SessionSettings,
353 state: Rc<RefCell<State<S>>>,
354 active_sessions: Rc<RefCell<ActiveSessionsMap<S>>>,
355 emitter: Emitter,
356 pending_logout: PendingLogout,
357 peer_addr: SocketAddr,
358) where
359 S: MessagesStorage,
360{
361 let (source, sink) = tcp_stream.into_split();
362 state.borrow_mut().set_disconnected(false);
363 let session_id = session_settings.session_id.clone();
364
365 let (sender, receiver) = mpsc::unbounded_channel();
366 let sender = Sender::new(sender);
367
368 let (disconnect_tx, disconnect_rx) = oneshot::channel();
369
370 register_sender(session_id.clone(), sender.clone());
371
372 let _cleanup_guard = SessionCleanupGuard {
373 session_id: session_id.clone(),
374 state: state.clone(),
375 active_sessions: active_sessions.clone(),
376 pending_logout,
377 reset_on_disconnect: session_settings.reset_on_disconnect,
378 };
379
380 let session = Rc::new(Session::new(
381 settings,
382 session_settings,
383 state,
384 sender,
385 emitter.clone(),
386 disconnect_tx,
387 peer_addr,
388 ));
389 active_sessions
390 .borrow_mut()
391 .insert(session_id.clone(), session.clone());
392
393 let session_span = info_span!(
394 "session",
395 id = %session_id
396 );
397
398 let input_loop_span = info_span!(parent: &session_span, "in");
399 let output_loop_span = info_span!(parent: &session_span, "out");
400
401 emitter
403 .send(FixEventInternal::Created(session_id.clone()))
404 .await;
405
406 let input_timeout_duration = session.heartbeat_interval().mul_f32(TEST_REQUEST_THRESHOLD);
407 let input_stream = timeout_stream(input_timeout_duration, input_stream(source))
408 .map(|res| res.unwrap_or(InputEvent::Timeout));
409 pin_mut!(input_stream);
410
411 let output_stream = output_stream(session.clone(), session.heartbeat_interval(), receiver);
412 pin_mut!(output_stream);
413
414 session.send_logon_request(&mut session.state().borrow_mut());
417
418 let connection = Connection::new(session);
419 let (input_closed_tx, input_closed_rx) = oneshot::channel();
420
421 tokio::join!(
422 connection
423 .input_loop(input_stream, input_closed_tx, None, disconnect_rx)
424 .instrument(input_loop_span),
425 connection
426 .output_loop(sink, output_stream, input_closed_rx)
427 .instrument(output_loop_span),
428 );
429 info!("connection closed");
430}
431
432impl<S: MessagesStorage> Connection<S> {
433 fn new(session: Rc<Session<S>>) -> Connection<S> {
434 Connection { session }
435 }
436
437 async fn input_loop(
438 &self,
439 mut input_stream: impl Stream<Item = InputEvent> + Unpin,
440 input_closed_tx: oneshot::Sender<()>,
441 force_disconnection_with_reason: Option<DisconnectReason>,
442 mut disconnect_rx: oneshot::Receiver<()>,
443 ) {
444 if let Some(disconnect_reason) = force_disconnection_with_reason {
445 self.session
446 .disconnect(&mut self.session.state().borrow_mut(), disconnect_reason);
447
448 input_closed_tx
452 .send(())
453 .expect("Failed to notify about closed inpuot");
454
455 return;
456 }
457
458 let mut disconnect_reason = DisconnectReason::Disconnected;
459 let mut logout_deadline = None;
460
461 let mut next_item = async || {
462 if logout_deadline.is_none() {
463 logout_deadline = self.session.logout_deadline();
464 }
465 if let Some(logout_deadline) = logout_deadline {
466 timeout_at(logout_deadline, input_stream.next())
467 .await
468 .unwrap_or(Some(InputEvent::LogoutTimeout))
469 } else {
470 input_stream.next().await
471 }
472 };
473
474 loop {
475 let event = tokio::select! {
476 event = next_item() => {
478 if let Some(event) = event {
479 event
481 } else {
482 break
483 }
484 }
485
486 _ = &mut disconnect_rx => {
488 info!("Disconnect signaled, exiting input loop");
489 disconnect_reason = DisconnectReason::ApplicationForcedDisconnect;
490 break;
491 }
492 };
493
494 if self.session.state().borrow().disconnected() {
496 info!("session disconnected, exit input processing");
497 input_closed_tx
501 .send(())
502 .expect("Failed to notify about closed input");
503 return;
504 }
505
506 match event {
507 InputEvent::Message(msg) => {
508 if let Some(reason) = self.session.on_message_in(msg).await {
509 info!(?reason, "disconnect, exit input processing");
510 disconnect_reason = reason;
511 break;
512 }
513 }
514 InputEvent::DeserializeError(error) => {
515 if let Some(reason) = self.session.on_deserialize_error(error).await {
516 info!(?reason, "disconnect, exit input processing");
517 disconnect_reason = reason;
518 break;
519 }
520 }
521 InputEvent::IoError(error) => {
522 error!(%error, "Input error");
523 disconnect_reason = DisconnectReason::IoError;
524 break;
525 }
526 InputEvent::Timeout => {
527 if self.session.on_in_timeout().await {
528 self.session.send_logout(
529 &mut self.session.state().borrow_mut(),
530 Some(SessionStatus::SessionLogoutComplete),
531 Some(FixString::from_ascii_lossy(
532 b"Grace period is over".to_vec(),
533 )),
534 );
535 break;
536 }
537 }
538 InputEvent::LogoutTimeout => {
539 info!("Logout timeout");
540 disconnect_reason = DisconnectReason::LogoutTimeout;
541 break;
542 }
543 }
544 }
545 self.session
546 .disconnect(&mut self.session.state().borrow_mut(), disconnect_reason);
547
548 input_closed_tx
552 .send(())
553 .expect("Failed to notify about closed inpout");
554 }
555
556 async fn output_loop(
557 &self,
558 mut sink: impl AsyncWrite + Unpin,
559 mut output_stream: impl Stream<Item = OutputEvent> + Unpin,
560 input_closed_rx: oneshot::Receiver<()>,
561 ) {
562 let mut sink_closed = false;
563 let mut disconnect_reason = DisconnectReason::Disconnected;
564 while let Some(event) = output_stream.next().await {
565 match event {
566 OutputEvent::Message(msg) => {
567 if sink_closed {
568 info!("Client disconnected, message will be stored for further resend");
573 } else if let Err(error) = sink.write_all(&msg).await {
574 sink_closed = true;
575 error!(%error, "Output write error");
576 }
588 }
589 OutputEvent::Timeout => self.session.on_out_timeout().await,
590 OutputEvent::Disconnect(reason) => {
591 info!("Client disconnected");
595 if !sink_closed && let Err(error) = sink.flush().await {
596 error!(%error, "final flush failed");
597 }
598 disconnect_reason = reason;
599 }
600 }
601 }
602 self.session.emit_logout(disconnect_reason).await;
606
607 let _ = input_closed_rx.await;
611 if let Err(error) = sink.shutdown().await {
612 error!(%error, "connection shutdown failed")
613 }
614 info!("disconnect, exit output processing");
615 }
616}