moonpool_sim/network/sim/
stream.rs1use super::types::ConnectionId;
2use crate::TcpListenerTrait;
3use crate::sim::state::CloseReason;
4use crate::{Event, WeakSimWorld};
5use futures::io::{AsyncRead, AsyncWrite};
6use std::{
7 future::Future,
8 io::{self, IoSlice},
9 pin::Pin,
10 task::{Context, Poll},
11};
12use tracing::instrument;
13
14fn sim_shutdown_error() -> io::Error {
18 io::Error::new(io::ErrorKind::BrokenPipe, "simulation shutdown")
19}
20
21fn random_connection_failure_error() -> io::Error {
23 io::Error::new(
24 io::ErrorKind::ConnectionReset,
25 "Random connection failure (explicit)",
26 )
27}
28
29fn half_open_timeout_error() -> io::Error {
31 io::Error::new(
32 io::ErrorKind::ConnectionReset,
33 "Connection reset (half-open timeout)",
34 )
35}
36
37fn connection_aborted_error() -> io::Error {
39 io::Error::new(
40 io::ErrorKind::ConnectionReset,
41 "Connection was aborted (RST)",
42 )
43}
44
45pub struct SimTcpStream {
115 sim: WeakSimWorld,
121
122 connection_id: ConnectionId,
128}
129
130impl SimTcpStream {
131 pub(crate) fn new(sim: WeakSimWorld, connection_id: ConnectionId) -> Self {
133 Self { sim, connection_id }
134 }
135
136 #[must_use]
138 pub fn connection_id(&self) -> ConnectionId {
139 self.connection_id
140 }
141
142 #[must_use]
146 pub fn is_write_vectored(&self) -> bool {
147 true
148 }
149
150 fn write_guard_pre_backpressure(
154 &self,
155 sim: &crate::sim::SimWorld,
156 cx: &mut Context<'_>,
157 ) -> Option<Poll<Result<usize, io::Error>>> {
158 if let Some(true) = sim.roll_random_close(self.connection_id) {
162 return Some(Poll::Ready(Err(random_connection_failure_error())));
164 }
166
167 if sim.is_send_closed(self.connection_id) {
169 return Some(Poll::Ready(Err(io::Error::new(
170 io::ErrorKind::BrokenPipe,
171 "Connection send side closed",
172 ))));
173 }
174
175 if sim.is_connection_closed(self.connection_id) {
177 return Some(match sim.close_reason(self.connection_id) {
179 CloseReason::Aborted => Poll::Ready(Err(connection_aborted_error())),
180 _ => Poll::Ready(Err(io::Error::new(
181 io::ErrorKind::BrokenPipe,
182 "Connection was closed (FIN)",
183 ))),
184 });
185 }
186
187 if sim.is_connection_cut(self.connection_id) {
188 tracing::debug!(
190 "SimTcpStream::poll_write connection_id={} is cut, registering cut waker",
191 self.connection_id.0
192 );
193 sim.register_cut_waker(self.connection_id, cx.waker().clone());
194 tracing::debug!(
195 "SimTcpStream::poll_write connection_id={} registered waker for cut connection",
196 self.connection_id.0
197 );
198 return Some(Poll::Pending);
199 }
200
201 if sim.is_half_open(self.connection_id) && sim.should_half_open_error(self.connection_id) {
203 tracing::debug!(
205 "SimTcpStream::poll_write connection_id={} half-open error time reached, returning ECONNRESET",
206 self.connection_id.0
207 );
208 return Some(Poll::Ready(Err(half_open_timeout_error())));
209 }
210 None
214 }
215
216 fn write_guard_clog(
220 &self,
221 sim: &crate::sim::SimWorld,
222 cx: &mut Context<'_>,
223 ) -> Option<Poll<Result<usize, io::Error>>> {
224 if sim.is_write_clogged(self.connection_id) {
226 sim.register_clog_waker(self.connection_id, cx.waker().clone());
228 return Some(Poll::Pending);
229 }
230
231 if sim.should_clog_write(self.connection_id) {
233 sim.clog_write(self.connection_id);
234 sim.register_clog_waker(self.connection_id, cx.waker().clone());
235 return Some(Poll::Pending);
236 }
237
238 None
239 }
240}
241
242impl Drop for SimTcpStream {
243 fn drop(&mut self) {
244 if let Ok(sim) = self.sim.upgrade() {
247 tracing::debug!(
248 "SimTcpStream dropping, closing connection {}",
249 self.connection_id.0
250 );
251 sim.close_connection(self.connection_id);
252 }
253 }
254}
255
256impl AsyncRead for SimTcpStream {
257 #[instrument(skip(self, cx, buf))]
258 fn poll_read(
259 self: Pin<&mut Self>,
260 cx: &mut Context<'_>,
261 buf: &mut [u8],
262 ) -> Poll<io::Result<usize>> {
263 tracing::trace!(
264 "SimTcpStream::poll_read called on connection_id={}",
265 self.connection_id.0
266 );
267 let sim = self.sim.upgrade().map_err(|_| sim_shutdown_error())?;
268
269 if let Some(true) = sim.roll_random_close(self.connection_id) {
273 return Poll::Ready(Err(random_connection_failure_error()));
275 }
277
278 if sim.is_recv_closed(self.connection_id) {
280 tracing::debug!(
281 "SimTcpStream::poll_read connection_id={} recv side closed, returning EOF",
282 self.connection_id.0
283 );
284 return Poll::Ready(Ok(0)); }
286
287 if sim.is_half_open(self.connection_id) && sim.should_half_open_error(self.connection_id) {
289 tracing::debug!(
291 "SimTcpStream::poll_read connection_id={} half-open error time reached, returning ECONNRESET",
292 self.connection_id.0
293 );
294 return Poll::Ready(Err(half_open_timeout_error()));
295 }
296 if sim.is_read_clogged(self.connection_id) {
301 sim.register_read_clog_waker(self.connection_id, cx.waker().clone());
303 return Poll::Pending;
304 }
305
306 if sim.should_clog_read(self.connection_id) {
308 sim.clog_read(self.connection_id);
309 sim.register_read_clog_waker(self.connection_id, cx.waker().clone());
310 return Poll::Pending;
311 }
312
313 let mut temp_buf = vec![0u8; buf.len()];
316 let bytes_read = sim
317 .read_from_connection(self.connection_id, &mut temp_buf)
318 .map_err(|e| io::Error::other(format!("read error: {e}")))?;
319
320 tracing::trace!(
321 "SimTcpStream::poll_read connection_id={} read {} bytes",
322 self.connection_id.0,
323 bytes_read
324 );
325
326 if bytes_read > 0 {
327 let data_preview = String::from_utf8_lossy(&temp_buf[..std::cmp::min(bytes_read, 20)]);
328 tracing::trace!(
329 "SimTcpStream::poll_read connection_id={} returning data: '{}'",
330 self.connection_id.0,
331 data_preview
332 );
333 buf[..bytes_read].copy_from_slice(&temp_buf[..bytes_read]);
334 return Poll::Ready(Ok(bytes_read));
335 }
336 Self::poll_read_no_data(&sim, self.connection_id, cx, buf)
337 }
338}
339
340impl SimTcpStream {
341 fn poll_read_no_data(
345 sim: &crate::sim::SimWorld,
346 connection_id: crate::network::sim::ConnectionId,
347 cx: &mut Context<'_>,
348 buf: &mut [u8],
349 ) -> Poll<io::Result<usize>> {
350 if sim.is_remote_fin_received(connection_id) {
352 tracing::info!(
353 "SimTcpStream::poll_read connection_id={} remote FIN received, returning EOF",
354 connection_id.0
355 );
356 return Poll::Ready(Ok(0));
357 }
358 if sim.is_connection_closed(connection_id) {
359 if sim.close_reason(connection_id) == CloseReason::Aborted {
360 tracing::info!(
361 "SimTcpStream::poll_read connection_id={} was aborted (RST)",
362 connection_id.0
363 );
364 return Poll::Ready(Err(connection_aborted_error()));
365 }
366 tracing::info!(
367 "SimTcpStream::poll_read connection_id={} closed gracefully (FIN)",
368 connection_id.0
369 );
370 return Poll::Ready(Ok(0));
371 }
372 if sim.is_connection_cut(connection_id) {
373 tracing::debug!(
374 "SimTcpStream::poll_read connection_id={} is cut, registering cut waker",
375 connection_id.0
376 );
377 sim.register_cut_waker(connection_id, cx.waker().clone());
378 return Poll::Pending;
379 }
380
381 tracing::trace!(
383 "SimTcpStream::poll_read connection_id={} no data, registering waker",
384 connection_id.0
385 );
386 sim.register_read_waker(connection_id, cx.waker().clone());
387
388 let mut temp_buf_recheck = vec![0u8; buf.len()];
389 let bytes_read_recheck = sim
390 .read_from_connection(connection_id, &mut temp_buf_recheck)
391 .map_err(|e| io::Error::other(format!("recheck read error: {e}")))?;
392
393 if bytes_read_recheck > 0 {
394 buf[..bytes_read_recheck].copy_from_slice(&temp_buf_recheck[..bytes_read_recheck]);
395 return Poll::Ready(Ok(bytes_read_recheck));
396 }
397
398 if sim.is_remote_fin_received(connection_id) {
400 return Poll::Ready(Ok(0));
401 }
402 if sim.is_connection_closed(connection_id) {
403 if sim.close_reason(connection_id) == CloseReason::Aborted {
404 return Poll::Ready(Err(connection_aborted_error()));
405 }
406 return Poll::Ready(Ok(0));
407 }
408 Poll::Pending
409 }
410}
411
412impl AsyncWrite for SimTcpStream {
413 #[instrument(skip(self, cx, buf))]
414 fn poll_write(
415 self: Pin<&mut Self>,
416 cx: &mut Context<'_>,
417 buf: &[u8],
418 ) -> Poll<Result<usize, io::Error>> {
419 let sim = self.sim.upgrade().map_err(|_| sim_shutdown_error())?;
420
421 if let Some(poll) = self.write_guard_pre_backpressure(&sim, cx) {
422 return poll;
423 }
424
425 let available_buffer = sim.available_send_buffer(self.connection_id);
427 if available_buffer < buf.len() {
428 tracing::debug!(
430 "SimTcpStream::poll_write connection_id={} buffer full (available={}, needed={}), waiting",
431 self.connection_id.0,
432 available_buffer,
433 buf.len()
434 );
435 sim.register_send_buffer_waker(self.connection_id, cx.waker().clone());
436 return Poll::Pending;
437 }
438
439 if let Some(poll) = self.write_guard_clog(&sim, cx) {
440 return poll;
441 }
442
443 let data_preview = String::from_utf8_lossy(&buf[..std::cmp::min(buf.len(), 20)]);
445 tracing::trace!(
446 "SimTcpStream::poll_write buffering {} bytes: '{}' for ordered delivery",
447 buf.len(),
448 data_preview
449 );
450
451 sim.buffer_send(self.connection_id, buf.to_vec())
453 .map_err(|e| io::Error::other(format!("buffer send error: {e}")))?;
454
455 Poll::Ready(Ok(buf.len()))
456 }
457
458 #[instrument(skip(self, cx, bufs))]
459 fn poll_write_vectored(
460 self: Pin<&mut Self>,
461 cx: &mut Context<'_>,
462 bufs: &[IoSlice<'_>],
463 ) -> Poll<Result<usize, io::Error>> {
464 let sim = self.sim.upgrade().map_err(|_| sim_shutdown_error())?;
465
466 if let Some(poll) = self.write_guard_pre_backpressure(&sim, cx) {
467 return poll;
468 }
469
470 let total: usize = bufs.iter().map(|slice| slice.len()).sum();
471 if total == 0 {
472 return Poll::Ready(Ok(0));
473 }
474
475 let available = sim.available_send_buffer(self.connection_id);
478 if available == 0 {
479 sim.register_send_buffer_waker(self.connection_id, cx.waker().clone());
480 return Poll::Pending;
481 }
482
483 if let Some(poll) = self.write_guard_clog(&sim, cx) {
484 return poll;
485 }
486
487 let accepted = total.min(available);
488
489 let mut remaining = accepted;
493 for slice in bufs {
494 if remaining == 0 {
495 break;
496 }
497 if slice.is_empty() {
498 continue;
499 }
500 let take = remaining.min(slice.len());
501 sim.buffer_send(self.connection_id, slice[..take].to_vec())
502 .map_err(|e| io::Error::other(format!("buffer send error: {e}")))?;
503 remaining -= take;
504 }
505
506 Poll::Ready(Ok(accepted))
507 }
508
509 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
510 Poll::Ready(Ok(()))
511 }
512
513 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
514 let sim = self.sim.upgrade().map_err(|_| sim_shutdown_error())?;
515
516 tracing::debug!(
518 "SimTcpStream::poll_close closing connection {}",
519 self.connection_id.0
520 );
521 sim.close_connection(self.connection_id);
522
523 Poll::Ready(Ok(()))
524 }
525}
526
527pub struct AcceptFuture {
529 sim: WeakSimWorld,
530 local_addr: String,
531}
532
533impl Future for AcceptFuture {
534 type Output = io::Result<(SimTcpStream, String)>;
535
536 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
537 let Ok(sim) = self.sim.upgrade() else {
538 return Poll::Ready(Err(sim_shutdown_error()));
539 };
540
541 let Some(connection_id) = sim.pending_connection(&self.local_addr) else {
542 sim.register_accept_waker(&self.local_addr, cx.waker().clone());
544 return Poll::Pending;
545 };
546
547 let delay = sim
549 .with_network_config(|config| crate::network::sample_latency(&config.accept_latency));
550
551 sim.schedule_event(
553 Event::Connection {
554 id: connection_id.0,
555 state: crate::ConnectionStateChange::ConnectionReady,
556 },
557 delay,
558 );
559
560 let peer_addr = sim
564 .connection_peer_address(connection_id)
565 .unwrap_or_else(|| "unknown:0".to_string());
566
567 let stream = SimTcpStream::new(self.sim.clone(), connection_id);
568 Poll::Ready(Ok((stream, peer_addr)))
569 }
570}
571
572pub struct SimTcpListener {
574 sim: WeakSimWorld,
575 local_addr: String,
576}
577
578impl SimTcpListener {
579 pub(crate) fn new(sim: WeakSimWorld, local_addr: String) -> Self {
581 Self { sim, local_addr }
582 }
583}
584
585impl TcpListenerTrait for SimTcpListener {
586 type TcpStream = SimTcpStream;
587
588 #[instrument(skip(self))]
589 async fn accept(&self) -> io::Result<(Self::TcpStream, String)> {
590 AcceptFuture {
591 sim: self.sim.clone(),
592 local_addr: self.local_addr.clone(),
593 }
594 .await
595 }
596
597 fn local_addr(&self) -> io::Result<String> {
598 Ok(self.local_addr.clone())
599 }
600}