Skip to main content

conmonrs/streaming_server/
session.rs

1//! What exec and attach sessions share: the connection to the client, its
2//! input and the final status.
3
4mod pending_stdin;
5
6use pending_stdin::PendingStdin;
7
8use super::{Session, StreamingServer};
9use crate::{
10    attach::SharedContainerAttach,
11    container_io::SharedContainerIO,
12    streaming_server::{
13        idle::Activity,
14        remotecommand::{
15            ClientEvent, ClientInput, ExpectedStreams, Framing, MESSAGE_TOO_LARGE, Operation,
16            Output, SpdyStreams, Status, Stdin, Version, WebSocketInput,
17        },
18        spdy::{self, Connection},
19        web::{self, WebSocket},
20    },
21};
22use anyhow::{Context, Result, bail};
23use bytes::Bytes;
24use futures::stream::StreamExt;
25use serde::{
26    Deserialize, Deserializer,
27    de::{IgnoredAny, MapAccess, Visitor},
28};
29use std::{
30    fmt,
31    future::Future,
32    ops::ControlFlow,
33    sync::{
34        Arc,
35        atomic::{AtomicBool, Ordering},
36    },
37};
38use tokio::{
39    select,
40    sync::{
41        mpsc::{self, Receiver as MpscReceiver, Sender as MpscSender},
42        watch,
43    },
44    task::{self, JoinHandle},
45    time::{self, Duration},
46};
47use tokio_util::sync::CancellationToken;
48use tracing::{Instrument, Span, debug, error, error_span, info, trace, warn};
49
50/// How much stdin the read task holds while the container does not take it,
51/// on top of the stdin channel. The socket is still read meanwhile, so that
52/// resize events and the close do not wait behind it. The message which
53/// fills it comes on top, also when it is larger than all of it.
54pub(super) const PENDING_STDIN_MAX_BYTES: usize = 1024 * 1024;
55
56/// How many entries of stdin wait for the session loop. Each is at most
57/// [`STDIN_CHUNK_SIZE`](pending_stdin::STDIN_CHUNK_SIZE), more of them would
58/// only hold more stdin which the container did not take.
59const STDIN_QUEUE_SIZE: usize = 1;
60
61/// How often a session pings the client, to notice a client which went away
62/// also while its stdin waits for the container. A WebSocket session pings
63/// only while stdin waits, a SPDY connection all the time.
64pub(super) const PING_INTERVAL: Duration = if cfg!(test) {
65    Duration::from_millis(100)
66} else {
67    spdy::PING_INTERVAL
68};
69
70/// How long the stdin which the client sent right before closing may wait for
71/// the container to take a part of it, see [`ClientRx::stdin_taken`]. A slow
72/// container gets all of it, only one which stopped reading its stdin holds
73/// up the end of the session this long.
74pub(super) const STDIN_FLUSH_TIMEOUT: Duration = if cfg!(test) {
75    Duration::from_secs(2)
76} else {
77    Duration::from_secs(5)
78};
79
80/// The latest resize event of a client, older ones are replaced.
81pub(super) type ResizeRx = watch::Receiver<Option<Bytes>>;
82
83/// How the input of a client ended.
84enum InputEnd<I> {
85    /// The client closed the connection cleanly.
86    Closed,
87
88    /// The client went away.
89    Gone,
90
91    /// The client sent a message larger than the server takes, its input
92    /// still has to drain the connection, see [`ClientInput::drain`].
93    TooLarge(I),
94}
95
96/// How a single event of a client ended its input, see
97/// [`StreamingServer::read_event`].
98#[derive(Debug, PartialEq)]
99enum EventEnd {
100    Closed,
101    TooLarge,
102}
103
104/// What the input of a client hands to the loop of its session.
105pub(super) struct ClientRx {
106    stdin: MpscReceiver<Stdin>,
107    resize: ResizeRx,
108
109    /// Cancelled once the client sent a message larger than the server
110    /// takes, which fails the session.
111    too_large: CancellationToken,
112
113    /// Touched whenever the container took a part of the stdin, at the clean
114    /// close of the client, and while the runtime starts an exec process
115    /// with stdin.
116    /// The stdin before the close gets forwarded until the container did not
117    /// take any of it for [`STDIN_FLUSH_TIMEOUT`].
118    stdin_taken: Activity,
119}
120
121#[cfg(test)]
122impl ClientRx {
123    /// The input of a client which sends nothing while the returned senders
124    /// live.
125    pub(super) fn idle() -> (Self, (MpscSender<Stdin>, watch::Sender<Option<Bytes>>)) {
126        let (stdin_tx, stdin) = mpsc::channel(STDIN_QUEUE_SIZE);
127        let (resize_tx, resize) = watch::channel(None);
128        let client = Self {
129            stdin,
130            resize,
131            too_large: CancellationToken::new(),
132            stdin_taken: Activity::new(),
133        };
134        (client, (stdin_tx, resize_tx))
135    }
136}
137
138/// Terminal resize event for exec and attach. Kubernetes decodes it with
139/// Go, which matches the names of the fields regardless of their case, so
140/// clients send `{"width":80}` as well as `{"Width":80}`, and leaves a
141/// missing or null field at zero, and a null event all zero. For `Width` and
142/// `Height`, ASCII case folding accepts the same names as the Unicode case
143/// folding of Go, which only adds the Kelvin sign for `k` and the long s for
144/// `s`.
145#[derive(Debug, Default, PartialEq)]
146struct ResizeEvent {
147    width: u16,
148    height: u16,
149}
150
151impl ResizeEvent {
152    fn parse(data: &[u8]) -> Result<Self> {
153        let event: Option<Self> = serde_json::from_slice(data).context("unmarshal resize event")?;
154        Ok(event.unwrap_or_default())
155    }
156}
157
158impl<'de> Deserialize<'de> for ResizeEvent {
159    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
160        /// The fields in the order of the document, like Go, where the last
161        /// one of the names which match wins.
162        struct Fields;
163
164        impl<'de> Visitor<'de> for Fields {
165            type Value = ResizeEvent;
166
167            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168                f.write_str("a terminal size")
169            }
170
171            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<ResizeEvent, A::Error> {
172                let mut event = ResizeEvent::default();
173                while let Some(name) = map.next_key::<String>()? {
174                    let field = if name.eq_ignore_ascii_case("width") {
175                        &mut event.width
176                    } else if name.eq_ignore_ascii_case("height") {
177                        &mut event.height
178                    } else {
179                        map.next_value::<IgnoredAny>()?;
180                        continue;
181                    };
182                    if let Some(value) = map.next_value()? {
183                        *field = value;
184                    }
185                }
186                Ok(event)
187            }
188        }
189
190        deserializer.deserialize_map(Fields)
191    }
192}
193
194impl StreamingServer {
195    /// Handle a single WebSocket connection, in the span of its session.
196    pub(super) async fn handle_websocket(socket: WebSocket, session: Session, framing: Framing) {
197        let span = Self::session_span(&session);
198        Self::run_websocket(socket, session, framing)
199            .instrument(span)
200            .await;
201    }
202
203    /// Run the session of a WebSocket connection.
204    async fn run_websocket(socket: WebSocket, session: Session, framing: Framing) {
205        let discard = socket.get_ref().discarder();
206        let written = socket.get_ref().written();
207        let (sender, messages) = socket.split();
208        let activity = Activity::new();
209        let mut output = Output::websocket(
210            sender,
211            framing,
212            activity.clone(),
213            written.clone(),
214            session.common().idle_timeout,
215        );
216        // A client which went away already ends the session in its loop,
217        // which tears it down.
218        if let Err(e) = output
219            .announce(session.common().stdout, session.common().stderr)
220            .await
221        {
222            debug!("Unable to announce the session: {e:#}");
223        }
224        let input = WebSocketInput {
225            messages,
226            framing,
227            activity: activity.clone(),
228            written,
229            discard,
230        };
231        Self::run_session(output, input, session, activity).await;
232        debug!("Closing WebSocket connection");
233    }
234
235    /// Handle a connection which got upgraded to SPDY, in the span of its
236    /// session, which the tasks of the connection inherit: a protocol error
237    /// of the client names the container as well.
238    pub(super) async fn handle_spdy<T>(io: T, session: Session, version: Version)
239    where
240        T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
241    {
242        let span = Self::session_span(&session);
243        Self::run_spdy(io, session, version).instrument(span).await;
244    }
245
246    /// Wait for the streams of the client of a SPDY connection, then run the
247    /// session on them.
248    async fn run_spdy<T>(io: T, session: Session, version: Version)
249    where
250        T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
251    {
252        let terminal = session.terminal();
253        // A terminal merges stderr into stdout, Kubernetes clients do not
254        // open a stream for it then.
255        let expected = ExpectedStreams {
256            stdin: session.common().stdin,
257            stdout: session.common().stdout,
258            stderr: session.common().stderr && !terminal,
259        };
260        // The session ends the connection once it was idle, which reports
261        // the timeout to the client first.
262        let mut connection = Connection::serve(io, None, Some(PING_INTERVAL), move |headers| {
263            SpdyStreams::accept(headers, version)
264        });
265        let activity = connection.activity();
266        let streams = match SpdyStreams::wait(&mut connection, expected).await {
267            Ok(streams) => streams,
268            Err(e) => {
269                debug!("Unable to start the session: {e:#}");
270                connection.handle().abort();
271                return;
272            }
273        };
274        let (input, output) = streams.into_parts(connection, version).await;
275        let output = Output::spdy(output, session.common().idle_timeout);
276        Self::run_session(output, input, session, activity).await;
277        debug!("Closing SPDY connection");
278    }
279
280    /// The span of a session and of the tasks it starts, which shows the
281    /// container ID at every log level, like the span of a request for a
282    /// container does, see `enter_request` of the RPC interface.
283    fn session_span(session: &Session) -> Span {
284        error_span!("session", container_id = &*session.common().container_id)
285    }
286
287    /// Run a session until it ended or the client went away. `activity`
288    /// tells when the connection was active the last time, for the idle
289    /// timeout of the session.
290    async fn run_session(
291        output: Output,
292        input: impl ClientInput,
293        session: Session,
294        activity: Activity,
295    ) {
296        let peer_closed = output.peer_closed();
297        let (stdin_tx, stdin_rx) = mpsc::channel(STDIN_QUEUE_SIZE);
298        // Resize events go through their own channel, so that they do not
299        // queue behind stdin the container does not read. Only the latest
300        // size matters.
301        let (resize_tx, resize_rx) = watch::channel(None);
302        let too_large = CancellationToken::new();
303        let stdin_taken = Activity::new();
304        let client = ClientRx {
305            stdin: stdin_rx,
306            resize: resize_rx,
307            too_large: too_large.clone(),
308            stdin_taken: stdin_taken.clone(),
309        };
310
311        let mut send_task = Self::write_task(output, client, session, activity.clone());
312        let mut recv_task = Self::read_task(
313            input,
314            stdin_tx,
315            resize_tx,
316            peer_closed,
317            too_large,
318            stdin_taken.clone(),
319        );
320
321        tokio::select! {
322            rv_a = (&mut send_task) => {
323                let output = Self::sent(rv_a);
324                // The client answers the close of a WebSocket, the read task
325                // drops what it sends until then, see
326                // [`web::await_close_answer`]. A close which did not go out
327                // gets no answer. A SPDY connection ended already, which ends
328                // the read task. A client which sent a message which was too
329                // large, also one whose session failed for it, cannot answer
330                // anymore, the connection gets drained instead.
331                if let Some(output) = output {
332                    match time::timeout(web::CLOSE_TIMEOUT, &mut recv_task).await {
333                        Ok(Ok(InputEnd::TooLarge(input))) => input.drain(output).await,
334                        Ok(_) => {}
335                        Err(_) => debug!("Client did not answer the close in time"),
336                    }
337                }
338                recv_task.abort();
339            },
340            rv_b = (&mut recv_task) => {
341                match rv_b {
342                    // After a clean close the session forwards the stdin which
343                    // the client sent before, while the container takes it.
344                    // The read task forwarded it since the close already, the
345                    // wait goes on from the last part the container took.
346                    Ok(InputEnd::Closed) => {
347                        debug!("Client closed the connection cleanly, or after its stdin ended");
348                        if stdin_taken
349                            .until_stalled(stdin_taken.last(), STDIN_FLUSH_TIMEOUT, &mut send_task)
350                            .await
351                            .is_none()
352                        {
353                            debug!("Session did not end after the close, stopping it");
354                        }
355                    }
356                    // The session fails, the client still gets its status and
357                    // the close, which tells why, and what it still sends
358                    // gets drained, so that they do not get lost. A client
359                    // still busy with the rest of its message reads them only
360                    // once it got rid of it, see [`web::Discard`]. The loop
361                    // of the session ends right away, the status and the
362                    // close end once the client stopped reading, see
363                    // [`Output::close`].
364                    Ok(InputEnd::TooLarge(input)) => {
365                        info!("Client sent a message larger than the limit");
366                        if let Ok(Some(output)) = (&mut send_task).await {
367                            input.drain(output).await;
368                        }
369                    }
370                    // The session ends a SPDY connection once it sent
371                    // everything, which ends the input as well.
372                    Ok(InputEnd::Gone) if send_task.is_finished() => {
373                        Self::sent((&mut send_task).await);
374                    }
375                    Ok(InputEnd::Gone) => debug!("Client connection ended"),
376                    Err(e) => error!("Unable to receive messages: {e}"),
377                }
378                send_task.abort();
379            }
380        }
381    }
382
383    /// Log how the write task ended, return its output once the close went
384    /// out.
385    fn sent(result: Result<Option<Output>, task::JoinError>) -> Option<Output> {
386        match result {
387            Ok(output) => {
388                debug!("All messages sent");
389                output
390            }
391            Err(e) => {
392                error!("Unable to send messages: {e}");
393                None
394            }
395        }
396    }
397
398    /// Build a common write task based on the session type. The loop of the
399    /// session only decides its final status, which goes out after it, so
400    /// that the idle timeout never cuts a status off: a client gets exactly
401    /// one, or none if it went away. A session whose connection was idle for
402    /// its timeout fails, like any other session which did not end, so that
403    /// the client does not take it for a success, unless the process exited
404    /// already. An exec process gets killed then, nothing is left to end it.
405    /// The task returns the output once the close went out, see
406    /// [`Output::close`].
407    fn write_task(
408        sender: Output,
409        client: ClientRx,
410        session: Session,
411        activity: Activity,
412    ) -> JoinHandle<Option<Output>> {
413        let idle_timeout = session.common().idle_timeout;
414        let too_large = client.too_large.clone();
415        let task = async move {
416            let mut sender = sender;
417            let mut status = None;
418            let (result, operation) = match session {
419                Session::Exec(s) => (
420                    async {
421                        // The runtime runs in a task of its own. A client
422                        // which goes away meanwhile must not kill it halfway,
423                        // the process it started still gets registered, and
424                        // torn down once the task finished. A client which
425                        // sent too much fails the session meanwhile, it
426                        // does not wait for its status. The terminal gets
427                        // the size of the client meanwhile. The stdin which
428                        // the client sent before a close waits for the
429                        // process, the runtime does not count as a container
430                        // which stopped taking it while it runs (it gets
431                        // killed after its deadline). A clean close without
432                        // pending stdin waits for the runtime as well then,
433                        // the process gets torn down in order afterwards.
434                        // Without stdin, nothing waits.
435                        let stdin = s.common.stdin;
436                        let start = Self::start_exec(*s, client.resize.clone());
437                        let mut start = task::spawn(start.in_current_span());
438                        let period = STDIN_FLUSH_TIMEOUT / 2;
439                        let mut starting = time::interval_at(time::Instant::now() + period, period);
440                        let started = loop {
441                            select! {
442                                started = &mut start => break started.context("join exec start")??,
443                                () = too_large.cancelled() => bail!(MESSAGE_TOO_LARGE),
444                                _ = starting.tick(), if stdin => client.stdin_taken.touch(),
445                            }
446                        };
447                        // The client waited for the runtime, which is no
448                        // idleness of its own, and the stdin gets its time
449                        // from the start of the process.
450                        activity.touch();
451                        client.stdin_taken.touch();
452                        let kill = started.teardown.kill.clone();
453                        let exec = Self::exec_loop(started, &mut sender, client, &mut status);
454                        let exec = Self::until_too_large(exec, &too_large);
455                        Self::until_idle(exec, &activity, idle_timeout, || {
456                            kill.store(true, Ordering::Release);
457                        })
458                        .await
459                    }
460                    .await,
461                    Operation::Exec,
462                ),
463                Session::Attach(s) => (
464                    Self::until_idle(
465                        Self::until_too_large(
466                            Self::attach_loop(s, &mut sender, client, &mut status),
467                            &too_large,
468                        ),
469                        &activity,
470                        idle_timeout,
471                        || {},
472                    )
473                    .await,
474                    Operation::Attach,
475                ),
476            };
477
478            // The close tells clients why, also ones which do not read the
479            // status. Their session failed, its end does not wait for the
480            // idle timeout, see [`Output::close_too_large`].
481            if too_large.is_cancelled() {
482                sender.close_too_large();
483            }
484            if let Some(status) = Self::final_status(result, status, operation)
485                && let Err(e) = sender.status(&status).await
486            {
487                debug!("Unable to send the status: {e:#}");
488            }
489
490            // Also for a message which came while the status went out.
491            if too_large.is_cancelled() {
492                sender.close_too_large();
493            }
494            sender.close().await.then_some(sender)
495        };
496        task::spawn(task.in_current_span())
497    }
498
499    /// The one status a client gets, out of how the loop of its session
500    /// ended and the status the loop decided on, none for a client which
501    /// went away.
502    fn final_status(
503        result: Result<()>,
504        status: Option<Status>,
505        operation: Operation,
506    ) -> Option<Status> {
507        match result {
508            Ok(()) => status,
509            // The process exited before, its status tells the client.
510            Err(e) if status.is_some() => {
511                debug!("Sending the exit status despite: {e:#}");
512                status
513            }
514            // Otherwise the client sees a clean close and assumes success.
515            // Mostly the client caused it: a command which the container
516            // does not have, a container which is gone or a connection
517            // which was idle. The span of the session names the container.
518            Err(e) => {
519                warn!("Unable to run {operation}: {e:#}");
520                Some(Status::failed(operation, format_args!("{e:#}")))
521            }
522        }
523    }
524
525    /// Run the loop of a session until the client sent a message larger
526    /// than the server takes, which fails it. The loop may wait for a client
527    /// which does not read its output then, it gets dropped, so that the
528    /// status and the close go out right away.
529    async fn until_too_large(
530        run: impl Future<Output = Result<()>>,
531        too_large: &CancellationToken,
532    ) -> Result<()> {
533        select! {
534            result = run => result,
535            () = too_large.cancelled() => bail!(MESSAGE_TOO_LARGE),
536        }
537    }
538
539    /// Run the loop of a session until the connection was idle for the
540    /// timeout, which fails it. `on_idle` runs before the loop gets dropped.
541    async fn until_idle(
542        run: impl Future<Output = Result<()>>,
543        activity: &Activity,
544        timeout: Option<Duration>,
545        on_idle: impl FnOnce(),
546    ) -> Result<()> {
547        if let Some(result) = activity.until_idle(timeout, run, on_idle).await {
548            result
549        } else {
550            debug!("Closing idle connection");
551            bail!(
552                "no activity on the connection for the idle timeout of {:?}",
553                timeout.unwrap_or_default()
554            )
555        }
556    }
557
558    /// Build a common read task. Every message of the client it reads counts
559    /// as activity, also stdin. Once stdin backs up behind a process which
560    /// does not take it, the client is not read anymore and the connection
561    /// may go idle, like a spdystream connection whose reader waits. Its
562    /// pings get no answer meanwhile either, a WebSocket client which fails
563    /// without pongs, like the one of client-go after about a minute, ends
564    /// the session once the process does not take stdin for that long. The
565    /// pings sit behind the stdin in the socket, reading them would mean
566    /// buffering the stdin without a bound.
567    fn read_task<I>(
568        mut input: I,
569        stdin_tx: MpscSender<Stdin>,
570        resize_tx: watch::Sender<Option<Bytes>>,
571        peer_closed: Arc<AtomicBool>,
572        too_large: CancellationToken,
573        stdin_taken: Activity,
574    ) -> JoinHandle<InputEnd<I>>
575    where
576        I: ClientInput,
577    {
578        let task = async move {
579            let mut pending = PendingStdin::default();
580            let end = loop {
581                select! {
582                    permit = stdin_tx.reserve(), if !pending.is_empty() => match permit {
583                        Ok(permit) => permit.send(pending.pop()),
584                        // The session ended, nothing takes stdin anymore.
585                        Err(_) => pending.clear(),
586                    },
587                    // Once stdin got closed, nothing gets added anymore, so
588                    // keep reading for the end of the connection.
589                    event = input.next(), if !pending.is_full() || pending.closed => match event {
590                        Some(event) => {
591                            if let ControlFlow::Break(end) =
592                                Self::read_event(event, &mut pending, &resize_tx)
593                            {
594                                break Some(end);
595                            }
596                        }
597                        None => break None,
598                    },
599                }
600            };
601            match end {
602                Some(EventEnd::Closed) => peer_closed.store(true, Ordering::Release),
603                Some(EventEnd::TooLarge) => too_large.cancel(),
604                None => {}
605            }
606
607            // Stdin sent right before a clean close, like the close of the
608            // stream itself, still has to reach the container. Only while
609            // the container takes it, a container which does not read it
610            // must not delay the end of the session for long. A client which
611            // went away or failed the session gets nothing forwarded
612            // anymore. The client gets the answer to its close meanwhile,
613            // also when the session gets stopped after.
614            if end == Some(EventEnd::Closed) {
615                stdin_taken.touch();
616                let flush = async {
617                    while !pending.is_empty() {
618                        if stdin_tx.send(pending.pop()).await.is_err() {
619                            break;
620                        }
621                    }
622                };
623                let flush =
624                    stdin_taken.until_stalled(time::Instant::now(), STDIN_FLUSH_TIMEOUT, flush);
625                if tokio::join!(flush, input.answer_close()).0.is_none() {
626                    debug!("Dropping stdin the container did not take after the close");
627                }
628            }
629            match end {
630                Some(EventEnd::Closed) => InputEnd::Closed,
631                Some(EventEnd::TooLarge) => InputEnd::TooLarge(input),
632                None => InputEnd::Gone,
633            }
634        };
635        task::spawn(task.in_current_span())
636    }
637
638    /// Handle a single event of the client, break once its input ended.
639    fn read_event(
640        event: ClientEvent,
641        pending: &mut PendingStdin,
642        resize_tx: &watch::Sender<Option<Bytes>>,
643    ) -> ControlFlow<EventEnd> {
644        match event {
645            ClientEvent::Stdin(stdin) => pending.push(stdin),
646            ClientEvent::Resize(data) => {
647                resize_tx.send_replace(Some(data));
648            }
649            ClientEvent::Closed => return ControlFlow::Break(EventEnd::Closed),
650            ClientEvent::TooLarge => return ControlFlow::Break(EventEnd::TooLarge),
651        }
652        ControlFlow::Continue(())
653    }
654}
655
656/// Forwards the stdin and the resize events of a client to the process, for
657/// the loops of exec and attach sessions, which forward the output
658/// meanwhile.
659pub(super) struct SessionInput {
660    io: SharedContainerIO,
661    attach: SharedContainerAttach,
662    client: ClientRx,
663
664    /// Stdin which waits for the container.
665    pending: Option<Bytes>,
666
667    /// Pings the client while stdin waits.
668    ping: time::Interval,
669
670    /// Whether stdin gets forwarded, until the client closed it.
671    pub(super) stdin_open: bool,
672}
673
674/// What the loop of a session has to handle.
675#[derive(Debug, PartialEq)]
676pub(super) enum InputEvent {
677    /// A resize event of the client, see [`SessionInput::resize`]. Resizing
678    /// does not depend on stdin, a terminal without it still has a size.
679    Resize(Bytes),
680
681    /// The stdin of the client is not read while it waits for the container,
682    /// so a client which goes away meanwhile is only noticed by a failing
683    /// send. The session pings it.
684    Ping,
685
686    /// The client closed its stdin.
687    StdinClosed,
688
689    /// The client closed the connection.
690    Ended,
691
692    /// The client sent a message larger than the server takes, which fails
693    /// the session.
694    TooLarge,
695}
696
697impl SessionInput {
698    /// Forward the input of `client` to the process of `io` and `attach`,
699    /// stdin only if `stdin` is set.
700    pub(super) fn new(
701        io: SharedContainerIO,
702        attach: SharedContainerAttach,
703        client: ClientRx,
704        stdin: bool,
705    ) -> Self {
706        let mut ping = time::interval_at(time::Instant::now() + PING_INTERVAL, PING_INTERVAL);
707        ping.set_missed_tick_behavior(time::MissedTickBehavior::Delay);
708        Self {
709            io,
710            attach,
711            client,
712            pending: None,
713            ping,
714            stdin_open: stdin,
715        }
716    }
717
718    /// Forward stdin until the session has something to handle. The session
719    /// may cancel it for its output, nothing gets lost then.
720    pub(super) async fn next(&mut self) -> InputEvent {
721        loop {
722            select! {
723                () = self.client.too_large.cancelled() => return InputEvent::TooLarge,
724                Ok(()) = self.client.resize.changed() => {
725                    if let Some(data) = self.client.resize.borrow_and_update().clone() {
726                        return InputEvent::Resize(data);
727                    }
728                }
729                // Wait for the container to take the stdin without blocking
730                // the output meanwhile.
731                () = self.attach.send_stdin(self.pending.clone().unwrap_or_default()), if self.pending.is_some() => {
732                    self.pending = None;
733                    self.client.stdin_taken.touch();
734                }
735                _ = self.ping.tick(), if self.pending.is_some() => return InputEvent::Ping,
736                stdin = self.client.stdin.recv(), if self.pending.is_none() => match stdin {
737                    // The input fails the session before it ends.
738                    None if self.client.too_large.is_cancelled() => return InputEvent::TooLarge,
739                    None => return InputEvent::Ended,
740                    Some(_) if !self.stdin_open => {}
741                    Some(Stdin::Data(data)) => self.pending = Some(data),
742                    Some(Stdin::Close) => {
743                        self.stdin_open = false;
744                        return InputEvent::StdinClosed;
745                    }
746                },
747            }
748        }
749    }
750
751    /// Close the stdin of the container after the stdin before. A terminal
752    /// cannot be closed without ending its output, it gets its end of file
753    /// character instead, like from a user pressing Ctrl-D. A process
754    /// reading lines sees the end of its input then, one after a line
755    /// without its ending needs a second one, like a user would type it.
756    pub(super) fn close_container_stdin(&mut self) {
757        // Right after `next` returned `StdinClosed`, which it only does while
758        // no stdin waits, so the end of file character replaces nothing.
759        debug_assert!(self.pending.is_none(), "stdin waits for the container");
760        match self.io.terminal_eof() {
761            Some(eof) => self.pending = Some(Bytes::from(vec![eof])),
762            None => {
763                self.attach.close_container_stdin();
764            }
765        }
766    }
767
768    /// Handle a resize event of the client, its JSON.
769    pub(super) fn resize(&self, data: &[u8]) {
770        Self::resize_with(data, |width, height| self.io.try_resize(width, height));
771    }
772
773    /// Handle a resize event of the client, its JSON, with `resize`, which
774    /// returns whether there is a terminal. Clients send them regardless of
775    /// a terminal, a broken one does not end the session.
776    pub(super) fn resize_with(data: &[u8], resize: impl FnOnce(u16, u16) -> Result<bool>) {
777        if let Err(e) = Self::try_resize(data, resize) {
778            debug!("Ignoring resize event: {e:#}");
779        }
780    }
781
782    fn try_resize(data: &[u8], resize: impl FnOnce(u16, u16) -> Result<bool>) -> Result<()> {
783        let e = ResizeEvent::parse(data)?;
784        trace!("Got resize message: {e:?}");
785        // Like CRI-O, a terminal never gets a size without rows or columns,
786        // which clients send before they know their own.
787        if e.width == 0 || e.height == 0 {
788            debug!("Ignoring resize event without a size: {e:?}");
789            return Ok(());
790        }
791        if !resize(e.width, e.height)? {
792            debug!("Ignoring resize event, container has no terminal");
793        }
794        Ok(())
795    }
796}
797
798/// The entry points of the fuzz targets.
799#[cfg(any(test, feature = "fuzzing"))]
800pub(super) mod fuzzing;
801
802#[cfg(test)]
803mod tests;