Skip to main content

hydra_sync/
server.rs

1use crate::protocol::{Role, perform_server_handshake, read_join_frame, read_raw_frame_into};
2use crate::session::Sessions;
3use crate::{BUFFER_SIZE, error, info, trace, warn};
4use anyhow::Result;
5use bytes::BytesMut;
6use std::net::SocketAddr;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::time::Duration;
10use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
11use tokio::net::{TcpListener, TcpStream};
12use tokio::sync::broadcast::error::RecvError;
13// TODO; handles backpressure "properly", implement handler traits for invoking user defined fn for some events, logging can be better.
14
15/// A light-weight multi-threaded SPMC (Single Producer Multiple Consumer) E2E relay server.
16///
17/// `HydraServer` implements a zero-copy [tokio::broadcast](https://docs.rs/tokio/latest/tokio/sync/broadcast/index.html) relay that:
18/// - Accepts one producer and multiple consumers per session
19/// - Routes data from producer → all connected consumers using Arc-backed `Bytes`
20/// - Handles backpressure and slow consumers with broadcast channel `RecvError::Lagged(n)` and slowing down producers scaled with channel buffer
21/// - Enforces connection limits and per-payload size constraints
22/// ```no_run
23/// use hydra_sync::server::HydraServer;
24///
25/// #[tokio::main]
26/// async fn main() {
27/// // choose an OS-assigned port
28///     let (server, addr) = HydraServer::bind_default().await.unwrap();
29///
30///     println!("Server running on: {}", addr);
31///     tokio::spawn(async move{ server.run(500).await });
32/// }
33/// ```
34/// Internals
35/// - Producer: Sends encrypted frames → broadcast channel
36/// - Consumers: Subscribe to broadcast, receive clones of `Arc<Bytes>` (zero-copy)
37/// - Sessions: Keyed by 64-byte session_id, one producer per session allowed
38/// - Errors & Logs: Error are predictable and handled gracefully by closing connections and logging without crashing the server
39///
40pub struct HydraServer {
41    /// internal tcp listener for accepting incoming connections
42    listener: TcpListener,
43    /// session management for producers and consumers
44    sessions: Arc<Sessions>,
45    /// atomic counter to track active connections for enforcing limits
46    connections: Arc<AtomicUsize>,
47    /// maximum concurrent connections allowed to prevent resource exhaustion
48    max_connections: usize,
49    /// maximum allowed payload size for incoming frames to prevent abuse
50    max_payload_length: usize,
51    /// capacity of the broadcast channel for each session to handle backpressure
52    max_channel_capacity: usize,
53}
54
55impl HydraServer {
56    /// Binds the relay server with defaults
57    /// - addr: OS-assigned port
58    /// - max_connections: 32
59    /// - max_payload_length: 64 MiB
60    /// - max_channel_capacity: 256 messages
61    pub async fn bind_default() -> Result<(Self, SocketAddr)> {
62        let addr = &"127.0.0.1:0".parse::<SocketAddr>()?;
63        let server = HydraServer::bind(addr, 64 * 1024 * 1024, 32, 256).await?;
64        let local_addr = server.listener.local_addr()?;
65        Ok((server, local_addr))
66    }
67
68    /// Binds the relay server to the specified socket address and initializes internal state
69    pub async fn bind(
70        addr: &SocketAddr,
71        max_payload_length: usize,
72        max_connections: usize,
73        max_channel_capacity: usize,
74    ) -> Result<Self> {
75        let listener = TcpListener::bind(addr).await?;
76        Ok(Self {
77            listener,
78            sessions: Arc::new(Sessions::init()),
79            connections: Arc::new(AtomicUsize::new(0)),
80            max_payload_length,
81            max_connections,
82            max_channel_capacity,
83        })
84    }
85
86    /// Main server loop to accept incoming connections, spawn thread handlers, perform handshakes & session creation
87    /// - `accept_timeout_ms` is the delay before client retries to accept new connections on server when the limit is reached
88    /// - Producer errors; If read fails from client or broadcast send fails, the connection is closed and the error is logged.
89    /// - Producer errors; If writing to client fails or broadcast lags or closed, the connection is closed and the error is logged.
90    /// - EOF check are gracefully handled by closing the connection without logging an error.
91    /// - `LOG_LEVEL` & `LOG_FILE` env vars can be set to control logging verbosity and output file (defaults to `info` level and stdout, not file).
92    pub async fn run(self, accept_timeout_ms: u64) -> Result<()> {
93        loop {
94            if self.connections.fetch_add(1, Ordering::Relaxed) >= self.max_connections {
95                self.connections.fetch_sub(1, Ordering::Relaxed);
96                warn!(
97                    "Max connections reached: {}, waiting {} ms before retrying",
98                    self.max_connections, accept_timeout_ms
99                );
100                tokio::time::sleep(Duration::from_millis(accept_timeout_ms)).await;
101                continue;
102            }
103
104            match self.listener.accept().await {
105                Ok((stream, peer_addr)) => {
106                    stream.set_nodelay(true).ok();
107                    let sessions = Arc::clone(&self.sessions);
108                    let connections = Arc::clone(&self.connections);
109                    // spawn handler thread
110                    tokio::spawn(async move {
111                        trace!("Accepted connection from: {}", peer_addr);
112                        if let Err(e) = Self::handle_connection(
113                            stream,
114                            sessions,
115                            self.max_payload_length,
116                            self.max_channel_capacity,
117                        )
118                        .await
119                        {
120                            error!("Connection handling error: {} from: {}", e, peer_addr);
121                        }
122                        connections.fetch_sub(1, Ordering::Release);
123                    });
124                }
125                Err(e) => {
126                    self.connections.fetch_sub(1, Ordering::Release);
127                    error!("Connection accepting error: {}", e);
128                }
129            }
130        }
131    }
132
133    /// Handles an individual client connection, performing handshake, role determination, and routing to producer/consumer handlers
134    async fn handle_connection(
135        mut stream: TcpStream,
136        sessions: Arc<Sessions>,
137        max_payload_length: usize,
138        broadcast_capacity: usize,
139    ) -> Result<()> {
140        stream.set_nodelay(true)?;
141        let mut mem_pool = BytesMut::with_capacity(max_payload_length + 4); // 4 bytes prefix space
142        let peer_addr = stream.peer_addr()?;
143        let (read_h, mut writer_raw) = stream.split();
144        let mut reader = BufReader::with_capacity(BUFFER_SIZE, read_h);
145
146        let transport_key = perform_server_handshake(&mut reader, &mut writer_raw).await?;
147        let (role, session_id) =
148            read_join_frame(&mut reader, &transport_key, &mut mem_pool).await?;
149
150        match role {
151            Role::Producer => {
152                info!(
153                    "Producer addr: {} joined session: {}",
154                    peer_addr,
155                    hex::encode(session_id)
156                );
157                Self::run_producer(
158                    &mut reader,
159                    sessions,
160                    session_id,
161                    &peer_addr,
162                    &mut mem_pool,
163                    max_payload_length,
164                    broadcast_capacity,
165                )
166                .await
167            }
168            Role::Consumer => {
169                info!(
170                    "Consumer addr: {} joined session: {}",
171                    peer_addr,
172                    hex::encode(session_id)
173                );
174                Self::run_consumer(
175                    &mut reader,
176                    &mut writer_raw,
177                    sessions,
178                    session_id,
179                    &peer_addr,
180                )
181                .await
182            }
183            Role::Admin => Ok(()), // todo; implement this
184        }
185    }
186
187    /// Handles producer clients: reads encrypted frames, decrypts, and broadcasts to consumers via the session's broadcast channel
188    async fn run_producer<R: AsyncReadExt + Unpin>(
189        reader: &mut R,
190        sessions: Arc<Sessions>,
191        session_id: [u8; 64],
192        client_addr: &SocketAddr,
193        mem_pool: &mut BytesMut,
194        max_payload_length: usize,
195        broadcast_capacity: usize,
196    ) -> Result<()> {
197        let tx = sessions.try_register_producer(session_id, broadcast_capacity)?;
198
199        const MAX_WAIT_MS: u64 = 12;
200
201        loop {
202            // read from client read stream (just channel, no intervention)
203            let frame_len =
204                match read_raw_frame_into(reader, mem_pool, max_payload_length).await {
205                    Ok(n) => n,
206                    Err(e) => {
207                        tx.closed().await;
208                        error!(
209                            "Producer addr: {} session: {} read: {e}",
210                            client_addr,
211                            hex::encode(session_id)
212                        );
213                        break;
214                    }
215                };
216
217            // TODO; change to this!!!
218            tokio::time::sleep(Duration::from_millis(
219                MAX_WAIT_MS * (frame_len / broadcast_capacity) as u64,
220            ))
221            .await;
222
223            // write to broadcast channel
224            let send_frame = mem_pool.split_to(frame_len).freeze();
225            if let Err(e) = tx.send(send_frame) {
226                tx.closed().await; // close channel to signal consumers
227                warn!(
228                    "Producer addr: {} session: {} broadcast: {e}",
229                    client_addr,
230                    hex::encode(session_id)
231                );
232                break;
233            }
234        }
235
236        // clean up
237        sessions.unregister_producer(session_id);
238        Ok(())
239    }
240
241    /// Handles consumer clients: subscribes to the session's broadcast channel and writes received data to the client
242    async fn run_consumer<R: AsyncReadExt + Unpin, W: AsyncWriteExt + Unpin>(
243        reader: &mut R,
244        writer: &mut W,
245        sessions: Arc<Sessions>,
246        session_id: [u8; 64],
247        client_addr: &SocketAddr,
248    ) -> Result<()> {
249        let tx = sessions
250            .get_for_consumer(session_id)
251            .ok_or_else(|| anyhow::anyhow!("Session not found"))?;
252
253        let mut rx = tx.subscribe();
254
255        let mut peek = [0u8; 1];
256        loop {
257            tokio::select! {
258                // poll from channel
259                result = rx.recv() => {
260                    match result {
261                        Ok(data) => {
262                            // try writing to client read stream first or fail
263                            if let Err(e) = writer.write_all(&data).await {
264                                let _ = writer.shutdown().await;
265                                error!("Consumer addr: {} session: {} write: {e}", client_addr, hex::encode(session_id));
266                                break;
267                            }
268                            // let _ = writer.flush().await;
269                        }
270                        Err(RecvError::Lagged(n)) => {
271                            let _ = writer.flush().await; // flush whatever remaining
272                            let _ = writer.shutdown().await;
273                            warn!("Consumer addr: {} session: {} lagged by {n} messages", client_addr, hex::encode(session_id));
274                            break;
275                        }
276                        Err(RecvError::Closed) => {
277                            let _ = writer.flush().await; // flush whatever b4 exiting
278                            let _ = writer.shutdown().await;
279                            info!("Producer for session: {} closed, consumer addr: {}", hex::encode(session_id), client_addr);
280                            break;
281                        },
282                    }
283                }
284                result = reader.read(&mut peek) => {
285                    match result {
286                        Ok(0) => break, // eof check
287                        Err(e) => {
288                            error!("Consumer addr: {} session: {} read: {e}", client_addr, hex::encode(session_id));
289                            break;
290                        }
291                        _ => {}
292                    }
293                }
294            }
295        }
296
297        Ok(())
298    }
299}