Skip to main content

h2/proto/
connection.rs

1use crate::codec::UserError;
2use crate::frame::{Reason, StreamId};
3use crate::{client, server};
4
5use crate::frame::DEFAULT_INITIAL_WINDOW_SIZE;
6use crate::proto::*;
7
8use bytes::Bytes;
9use futures_core::Stream;
10use std::io;
11use std::marker::PhantomData;
12use std::pin::Pin;
13use std::task::{Context, Poll};
14use std::time::Duration;
15use tokio::io::AsyncRead;
16
17/// An H2 connection
18#[derive(Debug)]
19pub(crate) struct Connection<T, P, B: Buf = Bytes>
20where
21    P: Peer,
22{
23    /// Read / write frame values
24    codec: Codec<T, Prioritized<B>>,
25
26    inner: ConnectionInner<P, B>,
27}
28
29// Extracted part of `Connection` which does not depend on `T`. Reduces the amount of duplicated
30// method instantiations.
31#[derive(Debug)]
32struct ConnectionInner<P, B: Buf = Bytes>
33where
34    P: Peer,
35{
36    /// Tracks the connection level state transitions.
37    state: State,
38
39    /// An error to report back once complete.
40    ///
41    /// This exists separately from State in order to support
42    /// graceful shutdown.
43    error: Option<frame::GoAway>,
44
45    /// Pending GOAWAY frames to write.
46    go_away: GoAway,
47
48    /// Ping/pong handler
49    ping_pong: PingPong,
50
51    /// Connection settings
52    settings: Settings,
53
54    /// Stream state handler
55    streams: Streams<B, P>,
56
57    /// A `tracing` span tracking the lifetime of the connection.
58    span: tracing::Span,
59
60    /// Client or server
61    _phantom: PhantomData<P>,
62}
63
64struct DynConnection<'a, B: Buf = Bytes> {
65    state: &'a mut State,
66
67    go_away: &'a mut GoAway,
68
69    streams: DynStreams<'a, B>,
70
71    error: &'a mut Option<frame::GoAway>,
72
73    ping_pong: &'a mut PingPong,
74}
75
76#[derive(Debug, Clone)]
77pub(crate) struct Config {
78    pub next_stream_id: StreamId,
79    pub initial_max_send_streams: usize,
80    pub max_send_buffer_size: usize,
81    pub reset_stream_duration: Duration,
82    pub reset_stream_max: usize,
83    pub remote_reset_stream_max: usize,
84    pub local_error_reset_streams_max: Option<usize>,
85    pub settings: frame::Settings,
86    pub data_frame_budget: usize,
87}
88
89#[derive(Clone, Copy, Debug)]
90pub(crate) enum DataFrameBudget {
91    Auto,
92    Configured(usize),
93}
94
95impl DataFrameBudget {
96    pub(crate) fn resolve(self, connection_window: Option<WindowSize>) -> usize {
97        match self {
98            Self::Configured(budget) => budget,
99            Self::Auto => {
100                let window = connection_window.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE);
101                let budget = window as usize / 2;
102
103                budget.max(DEFAULT_DATA_FRAME_BUDGET)
104            }
105        }
106    }
107}
108
109#[derive(Debug)]
110enum State {
111    /// Currently open in a sane state
112    Open,
113
114    /// The codec must be flushed
115    Closing(Reason, Initiator),
116
117    /// In a closed state
118    Closed(Reason, Initiator),
119}
120
121impl<T, P, B> Connection<T, P, B>
122where
123    T: AsyncRead + AsyncWrite + Unpin,
124    P: Peer,
125    B: Buf,
126{
127    pub fn new(codec: Codec<T, Prioritized<B>>, config: Config) -> Connection<T, P, B> {
128        fn streams_config(config: &Config) -> streams::Config {
129            streams::Config {
130                initial_max_send_streams: config.initial_max_send_streams,
131                local_max_buffer_size: config.max_send_buffer_size,
132                local_next_stream_id: config.next_stream_id,
133                local_push_enabled: config.settings.is_push_enabled().unwrap_or(true),
134                extended_connect_protocol_enabled: config
135                    .settings
136                    .is_extended_connect_protocol_enabled()
137                    .unwrap_or(false),
138                local_reset_duration: config.reset_stream_duration,
139                local_reset_max: config.reset_stream_max,
140                remote_reset_max: config.remote_reset_stream_max,
141                remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
142                remote_max_initiated: config
143                    .settings
144                    .max_concurrent_streams()
145                    .map(|max| max as usize),
146                local_max_error_reset_streams: config.local_error_reset_streams_max,
147                data_frame_budget: config.data_frame_budget,
148            }
149        }
150        let streams = Streams::new(streams_config(&config));
151        let span = tracing::debug_span!(parent: None, "Connection", peer = %P::NAME);
152        span.follows_from(tracing::Span::current());
153        Connection {
154            codec,
155            inner: ConnectionInner {
156                state: State::Open,
157                error: None,
158                go_away: GoAway::new(),
159                ping_pong: PingPong::new(),
160                settings: Settings::new(config.settings),
161                streams,
162                span,
163                _phantom: PhantomData,
164            },
165        }
166    }
167
168    /// connection flow control
169    pub(crate) fn set_target_window_size(&mut self, size: WindowSize) {
170        let _res = self.inner.streams.set_target_connection_window_size(size);
171        // TODO: proper error handling
172        debug_assert!(_res.is_ok());
173    }
174
175    /// Send a new SETTINGS frame with an updated initial window size.
176    pub(crate) fn set_initial_window_size(&mut self, size: WindowSize) -> Result<(), UserError> {
177        let mut settings = frame::Settings::default();
178        settings.set_initial_window_size(Some(size));
179        self.inner.settings.send_settings(settings)
180    }
181
182    /// Send a new SETTINGS frame with extended CONNECT protocol enabled.
183    pub(crate) fn set_enable_connect_protocol(&mut self) -> Result<(), UserError> {
184        let mut settings = frame::Settings::default();
185        settings.set_enable_connect_protocol(Some(1));
186        self.inner.settings.send_settings(settings)
187    }
188
189    /// Returns the maximum number of concurrent streams that may be initiated
190    /// by this peer.
191    pub(crate) fn max_send_streams(&self) -> usize {
192        self.inner.streams.max_send_streams()
193    }
194
195    /// Returns the maximum number of concurrent streams that may be initiated
196    /// by the remote peer.
197    pub(crate) fn max_recv_streams(&self) -> usize {
198        self.inner.streams.max_recv_streams()
199    }
200
201    #[cfg(feature = "unstable")]
202    pub fn num_wired_streams(&self) -> usize {
203        self.inner.streams.num_wired_streams()
204    }
205
206    /// Returns `Ready` when the connection is ready to receive a frame.
207    ///
208    /// Returns `Error` as this may raise errors that are caused by delayed
209    /// processing of received frames.
210    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Error>> {
211        let _e = self.inner.span.enter();
212        let span = tracing::trace_span!("poll_ready");
213        let _e = span.enter();
214        // The order of these calls don't really matter too much
215        ready!(self.inner.ping_pong.send_pending_pong(cx, &mut self.codec))?;
216        ready!(self.inner.ping_pong.send_pending_ping(cx, &mut self.codec))?;
217        ready!(self
218            .inner
219            .settings
220            .poll_send(cx, &mut self.codec, &mut self.inner.streams))?;
221        ready!(self.inner.streams.send_pending_refusal(cx, &mut self.codec))?;
222
223        Poll::Ready(Ok(()))
224    }
225
226    /// Send any pending GOAWAY frames.
227    ///
228    /// This will return `Some(reason)` if the connection should be closed
229    /// afterwards. If this is a graceful shutdown, this returns `None`.
230    fn poll_go_away(&mut self, cx: &mut Context) -> Poll<Option<io::Result<Reason>>> {
231        self.inner.go_away.send_pending_go_away(cx, &mut self.codec)
232    }
233
234    pub fn go_away_from_user(&mut self, e: Reason) {
235        self.inner.as_dyn().go_away_from_user(e)
236    }
237
238    fn take_error(&mut self, ours: Reason, initiator: Initiator) -> Result<(), Error> {
239        let (debug_data, theirs) = self
240            .inner
241            .error
242            .take()
243            .as_ref()
244            .map_or((Bytes::new(), Reason::NO_ERROR), |frame| {
245                (frame.debug_data().clone(), frame.reason())
246            });
247
248        match (ours, theirs) {
249            (Reason::NO_ERROR, Reason::NO_ERROR) => Ok(()),
250            (ours, Reason::NO_ERROR) => Err(Error::GoAway(Bytes::new(), ours, initiator)),
251            // If both sides reported an error, give their
252            // error back to th user. We assume our error
253            // was a consequence of their error, and less
254            // important.
255            (_, theirs) => Err(Error::remote_go_away(debug_data, theirs)),
256        }
257    }
258
259    /// Closes the connection by transitioning to a GOAWAY state
260    /// iff there are no streams or references
261    pub fn maybe_close_connection_if_no_streams(&mut self) {
262        // If we poll() and realize that there are no streams or references
263        // then we can close the connection by transitioning to GOAWAY
264        if !self.inner.streams.has_streams_or_other_references() {
265            self.inner.as_dyn().go_away_now(Reason::NO_ERROR);
266        }
267    }
268
269    /// Checks if there are any streams
270    pub fn has_streams(&self) -> bool {
271        self.inner.streams.has_streams()
272    }
273
274    /// Checks if there are any streams or references left
275    pub fn has_streams_or_other_references(&self) -> bool {
276        // If we poll() and realize that there are no streams or references
277        // then we can close the connection by transitioning to GOAWAY
278        self.inner.streams.has_streams_or_other_references()
279    }
280
281    pub(crate) fn take_user_pings(&mut self) -> Option<UserPings> {
282        self.inner.ping_pong.take_user_pings()
283    }
284
285    /// Advances the internal state of the connection.
286    pub fn poll(&mut self, cx: &mut Context) -> Poll<Result<(), Error>> {
287        // XXX(eliza): cloning the span is unfortunately necessary here in
288        // order to placate the borrow checker — `self` is mutably borrowed by
289        // `poll2`, which means that we can't borrow `self.span` to enter it.
290        // The clone is just an atomic ref bump.
291        let span = self.inner.span.clone();
292        let _e = span.enter();
293        let span = tracing::trace_span!("poll");
294        let _e = span.enter();
295
296        loop {
297            tracing::trace!(connection.state = ?self.inner.state);
298            // TODO: probably clean up this glob of code
299            match self.inner.state {
300                // When open, continue to poll a frame
301                State::Open => {
302                    let result = match self.poll2(cx) {
303                        Poll::Ready(result) => result,
304                        // The connection is not ready to make progress
305                        Poll::Pending => {
306                            // Ensure all window updates have been sent.
307                            //
308                            // This will also handle flushing `self.codec`
309                            ready!(self.inner.streams.poll_complete(cx, &mut self.codec))?;
310
311                            if (self.inner.error.is_some()
312                                || self.inner.go_away.should_close_on_idle())
313                                && !self.inner.streams.has_streams()
314                            {
315                                self.inner.as_dyn().go_away_now(Reason::NO_ERROR);
316                                continue;
317                            }
318
319                            return Poll::Pending;
320                        }
321                    };
322
323                    self.inner.as_dyn().handle_poll2_result(result)?
324                }
325                State::Closing(reason, initiator) => {
326                    tracing::trace!("connection closing after flush");
327                    // Flush/shutdown the codec
328                    ready!(self.codec.shutdown(cx))?;
329
330                    // Transition the state to error
331                    self.inner.state = State::Closed(reason, initiator);
332                }
333                State::Closed(reason, initiator) => {
334                    return Poll::Ready(self.take_error(reason, initiator));
335                }
336            }
337        }
338    }
339
340    fn poll2(&mut self, cx: &mut Context) -> Poll<Result<(), Error>> {
341        // This happens outside of the loop to prevent needing to do a clock
342        // check and then comparison of the queue possibly multiple times a
343        // second (and thus, the clock wouldn't have changed enough to matter).
344        self.clear_expired_reset_streams();
345
346        loop {
347            // First, ensure that the `Connection` is able to receive a frame
348            //
349            // The order here matters:
350            // - poll_go_away may buffer a graceful shutdown GOAWAY frame
351            // - If it has, we've also added a PING to be sent in poll_ready
352            if let Some(reason) = ready!(self.poll_go_away(cx)?) {
353                if self.inner.go_away.should_close_now() {
354                    if self.inner.go_away.is_user_initiated() {
355                        // A user initiated abrupt shutdown shouldn't return
356                        // the same error back to the user.
357                        return Poll::Ready(Ok(()));
358                    } else {
359                        return Poll::Ready(Err(Error::library_go_away(reason)));
360                    }
361                }
362                // Only NO_ERROR should be waiting for idle
363                debug_assert_eq!(
364                    reason,
365                    Reason::NO_ERROR,
366                    "graceful GOAWAY should be NO_ERROR"
367                );
368            }
369            ready!(self.poll_ready(cx))?;
370
371            match self
372                .inner
373                .as_dyn()
374                .recv_frame(ready!(Pin::new(&mut self.codec).poll_next(cx)?))?
375            {
376                ReceivedFrame::Settings(frame) => {
377                    self.inner.settings.recv_settings(
378                        frame,
379                        &mut self.codec,
380                        &mut self.inner.streams,
381                    )?;
382                }
383                ReceivedFrame::Continue => (),
384                ReceivedFrame::Done => {
385                    return Poll::Ready(Ok(()));
386                }
387            }
388        }
389    }
390
391    fn clear_expired_reset_streams(&mut self) {
392        self.inner.streams.clear_expired_reset_streams();
393    }
394}
395
396impl<P, B> ConnectionInner<P, B>
397where
398    P: Peer,
399    B: Buf,
400{
401    fn as_dyn(&mut self) -> DynConnection<'_, B> {
402        let ConnectionInner {
403            state,
404            go_away,
405            streams,
406            error,
407            ping_pong,
408            ..
409        } = self;
410        let streams = streams.as_dyn();
411        DynConnection {
412            state,
413            go_away,
414            streams,
415            error,
416            ping_pong,
417        }
418    }
419}
420
421impl<B> DynConnection<'_, B>
422where
423    B: Buf,
424{
425    fn go_away(&mut self, id: StreamId, e: Reason) {
426        let frame = frame::GoAway::new(id, e);
427        self.streams.send_go_away(id);
428        self.go_away.go_away(frame);
429    }
430
431    fn go_away_now(&mut self, e: Reason) {
432        let last_processed_id = self.streams.last_processed_id();
433        let frame = frame::GoAway::new(last_processed_id, e);
434        self.go_away.go_away_now(frame);
435    }
436
437    fn go_away_now_data(&mut self, e: Reason, data: Bytes) {
438        let last_processed_id = self.streams.last_processed_id();
439        let frame = frame::GoAway::with_debug_data(last_processed_id, e, data);
440        self.go_away.go_away_now(frame);
441    }
442
443    fn go_away_from_user(&mut self, e: Reason) {
444        let last_processed_id = self.streams.last_processed_id();
445        let frame = frame::GoAway::new(last_processed_id, e);
446        self.go_away.go_away_from_user(frame);
447
448        // Notify all streams of reason we're abruptly closing.
449        self.streams.handle_error(Error::user_go_away(e));
450    }
451
452    fn handle_poll2_result(&mut self, result: Result<(), Error>) -> Result<(), Error> {
453        match result {
454            // The connection has shutdown normally
455            Ok(()) => {
456                *self.state = State::Closing(Reason::NO_ERROR, Initiator::Library);
457                Ok(())
458            }
459            // Attempting to read a frame resulted in a connection level
460            // error. This is handled by setting a GOAWAY frame followed by
461            // terminating the connection.
462            Err(Error::GoAway(debug_data, reason, initiator)) => {
463                self.handle_go_away(reason, debug_data, initiator);
464                Ok(())
465            }
466            // Attempting to read a frame resulted in a stream level error.
467            // Locally detected stream errors are reported to the peer with
468            // RST_STREAM. Remotely initiated resets have already been applied
469            // by the streams state machine and must not be echoed back.
470            Err(Error::Reset(id, reason, initiator)) => {
471                if initiator == Initiator::Remote {
472                    tracing::trace!(?id, ?reason, ?initiator, "stream reset");
473                    return Ok(());
474                }
475
476                debug_assert_eq!(initiator, Initiator::Library);
477                tracing::trace!(?id, ?reason, ?initiator, "stream error");
478                match self.streams.send_reset(id, reason) {
479                    Ok(()) => (),
480                    Err(crate::proto::error::GoAway { debug_data, reason }) => {
481                        self.handle_go_away(reason, debug_data, Initiator::Library);
482                    }
483                }
484                Ok(())
485            }
486            // Attempting to read a frame resulted in an I/O error. All
487            // active streams must be reset.
488            //
489            // TODO: Are I/O errors recoverable?
490            Err(Error::Io(kind, inner)) => {
491                tracing::debug!(error = ?kind, "Connection::poll; IO error");
492                let e = Error::Io(kind, inner);
493
494                // Reset all active streams
495                self.streams.handle_error(e.clone());
496
497                // Some client implementations drop the connections without notifying its peer
498                // Attempting to read after the client dropped the connection results in UnexpectedEof
499                // If as a server, we don't have anything more to send, just close the connection
500                // without error
501                //
502                // See https://github.com/hyperium/hyper/issues/3427
503                if self.streams.is_buffer_empty()
504                    && matches!(kind, io::ErrorKind::UnexpectedEof)
505                    && (self.streams.is_server()
506                        || self.error.as_ref().map(|f| f.reason() == Reason::NO_ERROR)
507                            == Some(true))
508                {
509                    *self.state = State::Closed(Reason::NO_ERROR, Initiator::Library);
510                    return Ok(());
511                }
512
513                // Return the error
514                Err(e)
515            }
516        }
517    }
518
519    fn handle_go_away(&mut self, reason: Reason, debug_data: Bytes, initiator: Initiator) {
520        let e = Error::GoAway(debug_data.clone(), reason, initiator);
521        tracing::debug!(error = ?e, "Connection::poll; connection error");
522
523        // We may have already sent a GOAWAY for this error,
524        // if so, don't send another, just flush and close up.
525        if self
526            .go_away
527            .going_away()
528            .map_or(false, |frame| frame.reason() == reason)
529        {
530            tracing::trace!("    -> already going away");
531            *self.state = State::Closing(reason, initiator);
532            return;
533        }
534
535        // Reset all active streams
536        self.streams.handle_error(e);
537        self.go_away_now_data(reason, debug_data);
538    }
539
540    fn recv_frame(&mut self, frame: Option<Frame>) -> Result<ReceivedFrame, Error> {
541        use crate::frame::Frame::*;
542        match frame {
543            Some(Headers(frame)) => {
544                tracing::trace!(?frame, "recv HEADERS");
545                self.streams.recv_headers(frame)?;
546            }
547            Some(Data(frame)) => {
548                tracing::trace!(?frame, "recv DATA");
549                self.streams.recv_data(frame)?;
550            }
551            Some(Reset(frame)) => {
552                tracing::trace!(?frame, "recv RST_STREAM");
553                self.streams.recv_reset(frame)?;
554            }
555            Some(PushPromise(frame)) => {
556                tracing::trace!(?frame, "recv PUSH_PROMISE");
557                self.streams.recv_push_promise(frame)?;
558            }
559            Some(Settings(frame)) => {
560                tracing::trace!(?frame, "recv SETTINGS");
561                return Ok(ReceivedFrame::Settings(frame));
562            }
563            Some(GoAway(frame)) => {
564                tracing::trace!(?frame, "recv GOAWAY");
565                // This should prevent starting new streams,
566                // but should allow continuing to process current streams
567                // until they are all EOS. Once they are, State should
568                // transition to GoAway.
569                self.streams.recv_go_away(&frame)?;
570                *self.error = Some(frame);
571            }
572            Some(Ping(frame)) => {
573                tracing::trace!(?frame, "recv PING");
574                let status = self.ping_pong.recv_ping(frame);
575                if status.is_shutdown() {
576                    assert!(
577                        self.go_away.is_going_away(),
578                        "received unexpected shutdown ping"
579                    );
580
581                    let last_processed_id = self.streams.last_processed_id();
582                    self.go_away(last_processed_id, Reason::NO_ERROR);
583                }
584            }
585            Some(WindowUpdate(frame)) => {
586                tracing::trace!(?frame, "recv WINDOW_UPDATE");
587                self.streams.recv_window_update(frame)?;
588            }
589            Some(Priority(frame)) => {
590                tracing::trace!(?frame, "recv PRIORITY");
591                // TODO: handle
592            }
593            None => {
594                tracing::trace!("codec closed");
595                self.streams.recv_eof(false).expect("mutex poisoned");
596                return Ok(ReceivedFrame::Done);
597            }
598        }
599        Ok(ReceivedFrame::Continue)
600    }
601}
602
603enum ReceivedFrame {
604    Settings(frame::Settings),
605    Continue,
606    Done,
607}
608
609impl<T, B> Connection<T, client::Peer, B>
610where
611    T: AsyncRead + AsyncWrite,
612    B: Buf,
613{
614    pub(crate) fn streams(&self) -> &Streams<B, client::Peer> {
615        &self.inner.streams
616    }
617}
618
619impl<T, B> Connection<T, server::Peer, B>
620where
621    T: AsyncRead + AsyncWrite + Unpin,
622    B: Buf,
623{
624    pub fn next_incoming(&mut self) -> Option<StreamRef<B>> {
625        self.inner.streams.next_incoming()
626    }
627
628    // Graceful shutdown only makes sense for server peers.
629    pub fn go_away_gracefully(&mut self) {
630        if self.inner.go_away.is_going_away() {
631            // No reason to start a new one.
632            return;
633        }
634
635        // According to http://httpwg.org/specs/rfc7540.html#GOAWAY:
636        //
637        // > A server that is attempting to gracefully shut down a connection
638        // > SHOULD send an initial GOAWAY frame with the last stream
639        // > identifier set to 2^31-1 and a NO_ERROR code. This signals to the
640        // > client that a shutdown is imminent and that initiating further
641        // > requests is prohibited. After allowing time for any in-flight
642        // > stream creation (at least one round-trip time), the server can
643        // > send another GOAWAY frame with an updated last stream identifier.
644        // > This ensures that a connection can be cleanly shut down without
645        // > losing requests.
646        self.inner.as_dyn().go_away(StreamId::MAX, Reason::NO_ERROR);
647
648        // We take the advice of waiting 1 RTT literally, and wait
649        // for a pong before proceeding.
650        self.inner.ping_pong.ping_shutdown();
651    }
652}
653
654impl<T, P, B> Drop for Connection<T, P, B>
655where
656    P: Peer,
657    B: Buf,
658{
659    fn drop(&mut self) {
660        // Ignore errors as this indicates that the mutex is poisoned.
661        let _ = self.inner.streams.recv_eof(true);
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668
669    #[test]
670    fn auto_data_frame_budget_scales_with_connection_window() {
671        assert_eq!(
672            DataFrameBudget::Auto.resolve(None),
673            DEFAULT_INITIAL_WINDOW_SIZE as usize / 2
674        );
675        assert_eq!(
676            DataFrameBudget::Auto.resolve(Some(DEFAULT_INITIAL_WINDOW_SIZE)),
677            DEFAULT_INITIAL_WINDOW_SIZE as usize / 2
678        );
679        assert_eq!(DataFrameBudget::Auto.resolve(Some(1024 * 1024)), 512 * 1024);
680    }
681
682    #[test]
683    fn auto_data_frame_budget_has_minimum() {
684        assert_eq!(
685            DataFrameBudget::Auto.resolve(Some(1)),
686            DEFAULT_DATA_FRAME_BUDGET
687        );
688        assert_eq!(
689            DataFrameBudget::Auto.resolve(Some(MAX_WINDOW_SIZE)),
690            MAX_WINDOW_SIZE as usize / 2
691        );
692    }
693
694    #[test]
695    fn configured_data_frame_budget_is_unchanged() {
696        assert_eq!(
697            DataFrameBudget::Configured(123).resolve(Some(MAX_WINDOW_SIZE)),
698            123
699        );
700    }
701}