Skip to main content

quickfix_tokio/
engine.rs

1//! The engine: builds sessions from settings, spawns their tasks, binds
2//! acceptor listeners, and runs initiator connect loops.
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use tokio::sync::oneshot;
8use tokio::task::JoinHandle;
9
10use crate::application::Application;
11use crate::datadictionary::DataDictionary;
12use crate::error::{Error, Result};
13use crate::log::LogFactory;
14use crate::session::{Command, Session, SessionHandle};
15use crate::session_id::SessionId;
16use crate::settings::{ConnectionType, Settings, TlsSettings};
17use crate::store::MessageStoreFactory;
18use crate::transport::{self, SessionKey, Tls};
19
20pub struct Engine {
21    handles: HashMap<SessionKey, SessionHandle>,
22    io_tasks: Vec<JoinHandle<()>>,
23}
24
25fn key_of(id: &SessionId) -> SessionKey {
26    (id.begin_string.clone(), id.sender_comp_id.clone(), id.target_comp_id.clone())
27}
28
29impl Engine {
30    /// Create all configured sessions and start their network activity:
31    /// acceptors listen, initiators dial (and keep re-dialing).
32    pub async fn start(
33        settings: &Settings,
34        app: Arc<dyn Application>,
35        store_factory: Arc<dyn MessageStoreFactory>,
36        log_factory: Arc<dyn LogFactory>,
37    ) -> Result<Engine> {
38        let configs = settings.session_configs()?;
39        if configs.is_empty() {
40            return Err(Error::Config("no [SESSION] sections configured".into()));
41        }
42
43        let mut handles = HashMap::new();
44        let mut io_tasks = Vec::new();
45        let mut acceptors_by_port: HashMap<
46            u16,
47            (HashMap<SessionKey, SessionHandle>, TlsSettings),
48        > = HashMap::new();
49        let mut dictionaries: HashMap<String, Arc<DataDictionary>> = HashMap::new();
50
51        for cfg in configs {
52            // App-message dictionary and admin-message dictionary. For FIXT
53            // these differ: app messages use transport+app merged, admin
54            // messages the transport dictionary alone.
55            let (dictionary, admin_dictionary) = if !cfg.use_data_dictionary {
56                (None, None)
57            } else {
58                match (&cfg.transport_data_dictionary, &cfg.app_data_dictionary) {
59                    (Some(transport), Some(app)) => {
60                        let merged_key = format!("{transport}+{app}");
61                        let (merged, transport_dd) = match (
62                            dictionaries.get(&merged_key),
63                            dictionaries.get(transport),
64                        ) {
65                            (Some(m), Some(t)) => (m.clone(), t.clone()),
66                            _ => {
67                                let t = Arc::new(DataDictionary::load(transport).await?);
68                                let a = DataDictionary::load(app).await?;
69                                let m = Arc::new((*t).clone().merged_with_app(&a));
70                                dictionaries.insert(merged_key, m.clone());
71                                dictionaries.insert(transport.clone(), t.clone());
72                                (m, t)
73                            }
74                        };
75                        (Some(merged), Some(transport_dd))
76                    }
77                    _ => match &cfg.data_dictionary {
78                        Some(path) => {
79                            let dd = match dictionaries.get(path) {
80                                Some(dd) => dd.clone(),
81                                None => {
82                                    let dd = Arc::new(DataDictionary::load(path).await?);
83                                    dictionaries.insert(path.clone(), dd.clone());
84                                    dd
85                                }
86                            };
87                            (Some(dd.clone()), Some(dd))
88                        }
89                        None => (None, None),
90                    },
91                }
92            };
93            let key = key_of(&cfg.session_id);
94            if handles.contains_key(&key) {
95                return Err(Error::Config(format!(
96                    "duplicate session {}",
97                    cfg.session_id
98                )));
99            }
100            let store = store_factory.create(&cfg.session_id)?;
101            let log = log_factory.create(&cfg.session_id)?;
102            let connection_type = cfg.connection_type;
103            let (host, port, reconnect) =
104                (cfg.socket_connect_host.clone(), cfg.socket_connect_port, cfg.reconnect_interval);
105            let accept_port = cfg.socket_accept_port;
106            let tls_settings = cfg.tls.clone();
107
108            let handle =
109                Session::spawn(cfg, store, log, app.clone(), dictionary, admin_dictionary);
110            handles.insert(key.clone(), handle.clone());
111
112            match connection_type {
113                ConnectionType::Initiator => {
114                    let tls = client_tls(&tls_settings, &host)?;
115                    io_tasks.push(tokio::spawn(transport::run_initiator(
116                        host, port, reconnect, handle, tls,
117                    )));
118                }
119                ConnectionType::Acceptor => {
120                    // Sessions sharing an acceptor port share one TLS config
121                    // (TLS is negotiated before the session is identified).
122                    let entry = acceptors_by_port
123                        .entry(accept_port)
124                        .or_insert_with(|| (HashMap::new(), tls_settings.clone()));
125                    entry.0.insert(key, handle);
126                }
127            }
128        }
129
130        for (port, (registry, tls_settings)) in acceptors_by_port {
131            let tls = server_tls(&tls_settings)?;
132            let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await?;
133            io_tasks
134                .push(tokio::spawn(transport::run_acceptor(listener, Arc::new(registry), tls)));
135        }
136
137        Ok(Engine { handles, io_tasks })
138    }
139
140    /// Handle for the session matching this BeginString + CompID pair.
141    pub fn session(
142        &self,
143        begin_string: &str,
144        sender_comp_id: &str,
145        target_comp_id: &str,
146    ) -> Option<SessionHandle> {
147        self.handles
148            .get(&(begin_string.to_owned(), sender_comp_id.to_owned(), target_comp_id.to_owned()))
149            .cloned()
150    }
151
152    pub fn sessions(&self) -> impl Iterator<Item = &SessionHandle> {
153        self.handles.values()
154    }
155
156    /// Graceful shutdown: log out every session, stop their tasks, and stop
157    /// listening/dialing.
158    pub async fn stop(self) {
159        for task in &self.io_tasks {
160            task.abort();
161        }
162        for handle in self.handles.values() {
163            let (tx, rx) = oneshot::channel();
164            if handle.cmd_tx.send(Command::Stop(tx)).await.is_ok() {
165                let _ = rx.await;
166            }
167        }
168    }
169}
170
171/// Build initiator-side TLS from a session's settings.
172fn client_tls(tls: &TlsSettings, host: &str) -> Result<Tls> {
173    if !tls.enabled {
174        return Ok(Tls::None);
175    }
176    #[cfg(feature = "tls")]
177    return Ok(Tls::Client(crate::tls::build_connector(tls, host)?));
178    #[cfg(not(feature = "tls"))]
179    {
180        let _ = host;
181        Err(Error::Config("SocketUseSSL=Y requires the 'tls' feature".into()))
182    }
183}
184
185/// Build acceptor-side TLS from a session's settings.
186fn server_tls(tls: &TlsSettings) -> Result<Tls> {
187    if !tls.enabled {
188        return Ok(Tls::None);
189    }
190    #[cfg(feature = "tls")]
191    return Ok(Tls::Server(crate::tls::build_acceptor(tls)?));
192    #[cfg(not(feature = "tls"))]
193    Err(Error::Config("SocketUseSSL=Y requires the 'tls' feature".into()))
194}