h3: add GOAWAY API

Previously, quiche did some basic GOAWAY frame reception validation
internally but did not expose a method to send GOAWAY, nor an event
to inform apps that a GOAWAY had been received.

On the sending side, this change adds the `send_goaway()` method
that an application can use to send a GOAWAY frame that informs the
peer of the intention to close the connection.

On the receiving side, this change adds the `h3::Event::GoAway`
event which is returned by `poll()`.

The GOAWAY frame contains an identifier field, which affects how an
application should continue using the HTTP/3 connection. If a client
receives the frame, it MUST NOT issue requests with a larger stream
ID than indicated. If a server receives the frame, it MUST NOT
issue a push.

Note that applications, not the quiche library, are responsible for
processing request and response messages. Therefore the application
must ensure that GOAWAYS are issued in a semantically valid way. For
instance, a server that sends GOAWAY for stream ID 100 commits to
processing all requests up to that stream ID, unless it explicitly
cancels them or further reduces the limit. An application that
processes requests on stream IDs beyond the limit will cause
problems that cannot be detected by quiche.
diff --git a/examples/http3-client.c b/examples/http3-client.c
index 4b829e5..8c75309 100644
--- a/examples/http3-client.c
+++ b/examples/http3-client.c
@@ -258,6 +258,11 @@
 
                 case QUICHE_H3_EVENT_DATAGRAM:
                     break;
+                    
+                case QUICHE_H3_EVENT_GOAWAY: {
+                    fprintf(stderr, "got GOAWAY\n");
+                    break;
+                }
             }
 
             quiche_h3_event_free(ev);
diff --git a/examples/http3-client.rs b/examples/http3-client.rs
index 505a1a4..a93d67e 100644
--- a/examples/http3-client.rs
+++ b/examples/http3-client.rs
@@ -260,6 +260,10 @@
 
                     Ok((_flow_id, quiche::h3::Event::Datagram)) => (),
 
+                    Ok((goaway_id, quiche::h3::Event::GoAway)) => {
+                        info!("GOAWAY id={}", goaway_id);
+                    },
+
                     Err(quiche::h3::Error::Done) => {
                         break;
                     },
diff --git a/examples/http3-server.c b/examples/http3-server.c
index fc8d830..b6b3041 100644
--- a/examples/http3-server.c
+++ b/examples/http3-server.c
@@ -427,6 +427,11 @@
 
                     case QUICHE_H3_EVENT_DATAGRAM:
                         break;
+
+                    case QUICHE_H3_EVENT_GOAWAY: {
+                        fprintf(stderr, "got GOAWAY\n");
+                        break;
+                    }
                 }
 
                 quiche_h3_event_free(ev);
diff --git a/examples/http3-server.rs b/examples/http3-server.rs
index a6efb57..4c41cbb 100644
--- a/examples/http3-server.rs
+++ b/examples/http3-server.rs
@@ -359,6 +359,8 @@
 
                         Ok((_flow_id, quiche::h3::Event::Datagram)) => (),
 
+                        Ok((_goaway_id, quiche::h3::Event::GoAway)) => (),
+
                         Err(quiche::h3::Error::Done) => {
                             break;
                         },
diff --git a/extras/nginx/nginx-1.16.patch b/extras/nginx/nginx-1.16.patch
index 5df8ee8..b8548b7 100644
--- a/extras/nginx/nginx-1.16.patch
+++ b/extras/nginx/nginx-1.16.patch
@@ -1559,7 +1559,7 @@
 index 000000000..358a36528
 --- /dev/null
 +++ b/src/http/v3/ngx_http_v3.c
-@@ -0,0 +1,2231 @@
+@@ -0,0 +1,2234 @@
 +
 +/*
 + * Copyright (C) Cloudflare, Inc.
@@ -2022,6 +2022,9 @@
 +
 +            case QUICHE_H3_EVENT_DATAGRAM:
 +                break;
++
++            case QUICHE_H3_EVENT_GOAWAY:
++                break;
 +        }
 +
 +        quiche_h3_event_free(ev);
diff --git a/include/quiche.h b/include/quiche.h
index 09c4484..8a25975 100644
--- a/include/quiche.h
+++ b/include/quiche.h
@@ -459,6 +459,7 @@
     QUICHE_H3_EVENT_DATA,
     QUICHE_H3_EVENT_FINISHED,
     QUICHE_H3_EVENT_DATAGRAM,
+    QUICHE_H3_EVENT_GOAWAY,
 };
 
 typedef struct Http3Event quiche_h3_event;
diff --git a/src/h3/ffi.rs b/src/h3/ffi.rs
index c0be041..2d278c7 100644
--- a/src/h3/ffi.rs
+++ b/src/h3/ffi.rs
@@ -113,6 +113,8 @@
         h3::Event::Finished { .. } => 2,
 
         h3::Event::Datagram { .. } => 3,
+
+        h3::Event::GoAway { .. } => 4,
     }
 }
 
diff --git a/src/h3/frame.rs b/src/h3/frame.rs
index 699bc8d..bee2b3b 100644
--- a/src/h3/frame.rs
+++ b/src/h3/frame.rs
@@ -67,7 +67,7 @@
     },
 
     GoAway {
-        stream_id: u64,
+        id: u64,
     },
 
     MaxPushId {
@@ -104,7 +104,7 @@
                 parse_push_promise(payload_length, &mut b)?,
 
             GOAWAY_FRAME_TYPE_ID => Frame::GoAway {
-                stream_id: b.get_varint()?,
+                id: b.get_varint()?,
             },
 
             MAX_PUSH_FRAME_TYPE_ID => Frame::MaxPushId {
@@ -206,11 +206,11 @@
                 b.put_bytes(header_block.as_ref())?;
             },
 
-            Frame::GoAway { stream_id } => {
+            Frame::GoAway { id } => {
                 b.put_varint(GOAWAY_FRAME_TYPE_ID)?;
-                b.put_varint(octets::varint_len(*stream_id) as u64)?;
+                b.put_varint(octets::varint_len(*id) as u64)?;
 
-                b.put_varint(*stream_id)?;
+                b.put_varint(*id)?;
             },
 
             Frame::MaxPushId { push_id } => {
@@ -263,8 +263,8 @@
                 )?;
             },
 
-            Frame::GoAway { stream_id } => {
-                write!(f, "GOAWAY stream_id={}", stream_id)?;
+            Frame::GoAway { id } => {
+                write!(f, "GOAWAY id={}", id)?;
             },
 
             Frame::MaxPushId { push_id } => {
@@ -588,7 +588,7 @@
     fn goaway() {
         let mut d = [42; 128];
 
-        let frame = Frame::GoAway { stream_id: 32 };
+        let frame = Frame::GoAway { id: 32 };
 
         let frame_payload_len = 1;
         let frame_header_len = 2;
diff --git a/src/h3/mod.rs b/src/h3/mod.rs
index 485e2e9..6248688 100644
--- a/src/h3/mod.rs
+++ b/src/h3/mod.rs
@@ -162,6 +162,10 @@
 //!
 //!         Ok((_flow_id, quiche::h3::Event::Datagram)) => (),
 //!
+//!         Ok((goaway_id, quiche::h3::Event::GoAway)) => {
+//!              // Peer signalled it is going away, handle it.
+//!         },
+//!
 //!         Err(quiche::h3::Error::Done) => {
 //!             // Done reading.
 //!             break;
@@ -211,6 +215,10 @@
 //!
 //!         Ok((_flow_id, quiche::h3::Event::Datagram)) => (),
 //!
+//!         Ok((goaway_id, quiche::h3::Event::GoAway)) => {
+//!              // Peer signalled it is going away, handle it.
+//!         },
+//!
 //!         Err(quiche::h3::Error::Done) => {
 //!             // Done reading.
 //!             break;
@@ -514,6 +522,9 @@
 
     /// DATAGRAM was received.
     Datagram,
+
+    /// GOAWAY was received.
+    GoAway,
 }
 
 struct ConnectionSettings {
@@ -553,6 +564,9 @@
     finished_streams: VecDeque<u64>,
 
     frames_greased: bool,
+
+    local_goaway_id: Option<u64>,
+    peer_goaway_id: Option<u64>,
 }
 
 impl Connection {
@@ -601,6 +615,9 @@
             finished_streams: VecDeque::new(),
 
             frames_greased: false,
+
+            local_goaway_id: None,
+            peer_goaway_id: None,
         })
     }
 
@@ -648,6 +665,12 @@
     pub fn send_request<T: NameValue>(
         &mut self, conn: &mut super::Connection, headers: &[T], fin: bool,
     ) -> Result<u64> {
+        // If we received a GOAWAY from the peer, MUST NOT initiate new
+        // requests.
+        if self.peer_goaway_id.is_some() {
+            return Err(Error::FrameUnexpected);
+        }
+
         let stream_id = self.next_request_stream_id;
 
         self.streams
@@ -995,6 +1018,10 @@
     ///
     /// The event [`Datagram`] returns a flow ID.
     ///
+    /// The event [`GoAway`] returns an ID that depends on the connection role.
+    /// A client receives the largest processed stream ID. A server receives the
+    /// the largest permitted push ID.
+    ///
     /// If an error occurs while processing data, the connection is closed with
     /// the appropriate error code, using the transport's [`close()`] method.
     ///
@@ -1003,6 +1030,7 @@
     /// [`Data`]: enum.Event.html#variant.Data
     /// [`Finished`]: enum.Event.html#variant.Finished
     /// [`Datagram`]: enum.Event.html#variant.Datagram
+    /// [`GoAway`]: enum.Event.html#variant.GoAWay
     /// [`recv_body()`]: struct.Connection.html#method.recv_body
     /// [`send_response()`]: struct.Connection.html#method.send_response
     /// [`send_body()`]: struct.Connection.html#method.send_body
@@ -1018,15 +1046,33 @@
 
         // Process control streams first.
         if let Some(stream_id) = self.peer_control_stream_id {
-            self.process_control_stream(conn, stream_id)?;
+            match self.process_control_stream(conn, stream_id) {
+                Ok(ev) => return Ok(ev),
+
+                Err(Error::Done) => (),
+
+                Err(e) => return Err(e),
+            };
         }
 
         if let Some(stream_id) = self.peer_qpack_streams.encoder_stream_id {
-            self.process_control_stream(conn, stream_id)?;
+            match self.process_control_stream(conn, stream_id) {
+                Ok(ev) => return Ok(ev),
+
+                Err(Error::Done) => (),
+
+                Err(e) => return Err(e),
+            };
         }
 
         if let Some(stream_id) = self.peer_qpack_streams.decoder_stream_id {
-            self.process_control_stream(conn, stream_id)?;
+            match self.process_control_stream(conn, stream_id) {
+                Ok(ev) => return Ok(ev),
+
+                Err(Error::Done) => (),
+
+                Err(e) => return Err(e),
+            };
         }
 
         // Process finished streams list.
@@ -1075,6 +1121,59 @@
         Err(Error::Done)
     }
 
+    /// Sends a GOAWAY frame to initiate graceful connection closure.
+    ///
+    /// When quiche is used in the server role, the `id` parameter is the stream
+    /// ID of the highest processed request. This can be any valid ID between 0
+    /// and 2^62-4. However, the ID cannot be increased. Failure to satisfy
+    /// these conditions will return an error.
+    ///
+    /// This method does not close the QUIC connection. Applications are
+    /// required to call [`close()`] themselves.
+    ///
+    /// [`close()`]: ../struct.Connection.html#method.close
+    pub fn send_goaway(
+        &mut self, conn: &mut super::Connection, id: u64,
+    ) -> Result<()> {
+        if !self.is_server {
+            // TODO: server push
+            return Ok(());
+        }
+
+        if self.is_server && id % 4 != 0 {
+            return Err(Error::IdError);
+        }
+
+        if let Some(sent_id) = self.local_goaway_id {
+            if id > sent_id {
+                return Err(Error::IdError);
+            }
+        }
+
+        if let Some(stream_id) = self.control_stream_id {
+            let mut d = [42; 10];
+            let mut b = octets::OctetsMut::with_slice(&mut d);
+
+            let frame = frame::Frame::GoAway { id };
+
+            let wire_len = frame.to_bytes(&mut b)?;
+            let stream_cap = conn.stream_capacity(stream_id)?;
+
+            if stream_cap < wire_len {
+                return Err(Error::StreamBlocked);
+            }
+
+            trace!("{} tx frm {:?}", conn.trace_id(), frame);
+
+            let off = b.off();
+            conn.stream_send(stream_id, &d[..off], false)?;
+
+            self.local_goaway_id = Some(id);
+        }
+
+        Ok(())
+    }
+
     fn open_uni_stream(
         &mut self, conn: &mut super::Connection, ty: u64,
     ) -> Result<u64> {
@@ -1236,7 +1335,7 @@
 
     fn process_control_stream(
         &mut self, conn: &mut super::Connection, stream_id: u64,
-    ) -> Result<()> {
+    ) -> Result<(u64, Event)> {
         if conn.stream_finished(stream_id) {
             conn.close(
                 true,
@@ -1248,7 +1347,7 @@
         }
 
         match self.process_readable_stream(conn, stream_id) {
-            Ok(_) => (),
+            Ok(ev) => return Ok(ev),
 
             Err(Error::Done) => (),
 
@@ -1265,7 +1364,7 @@
             return Err(Error::ClosedCriticalStream);
         }
 
-        Ok(())
+        Err(Error::Done)
     }
 
     fn process_readable_stream(
@@ -1568,19 +1667,7 @@
                 // Do nothing. The Data event is returned separately.
             },
 
-            frame::Frame::GoAway {
-                stream_id: goaway_stream_id,
-            } => {
-                if self.is_server {
-                    conn.close(
-                        true,
-                        Error::FrameUnexpected.to_wire(),
-                        b"GOWAY received on server",
-                    )?;
-
-                    return Err(Error::FrameUnexpected);
-                }
-
+            frame::Frame::GoAway { id } => {
                 if Some(stream_id) != self.peer_control_stream_id {
                     conn.close(
                         true,
@@ -1591,17 +1678,31 @@
                     return Err(Error::FrameUnexpected);
                 }
 
-                if goaway_stream_id % 4 != 0 {
+                if !self.is_server && id % 4 != 0 {
                     conn.close(
                         true,
                         Error::FrameUnexpected.to_wire(),
                         b"GOAWAY received with ID of non-request stream",
                     )?;
 
-                    return Err(Error::FrameUnexpected);
+                    return Err(Error::IdError);
                 }
 
-                // TODO: implement GOAWAY
+                if let Some(received_id) = self.peer_goaway_id {
+                    if id > received_id {
+                        conn.close(
+                            true,
+                            Error::IdError.to_wire(),
+                            b"GOAWAY received with ID larger than previously received",
+                        )?;
+
+                        return Err(Error::IdError);
+                    }
+                }
+
+                self.peer_goaway_id = Some(id);
+
+                return Ok((id, Event::GoAway));
             },
 
             frame::Frame::MaxPushId { push_id } => {
@@ -2514,21 +2615,17 @@
     }
 
     #[test]
-    /// Send a GOAWAY frame from the client, which is forbidden.
-    fn goaway_from_client() {
+    /// Send a GOAWAY frame from the client.
+    fn goaway_from_client_good() {
         let mut s = Session::default().unwrap();
         s.handshake().unwrap();
 
-        s.send_frame_client(
-            frame::Frame::GoAway { stream_id: 100 },
-            s.client.control_stream_id.unwrap(),
-            false,
-        )
-        .unwrap();
+        s.client.send_goaway(&mut s.pipe.client, 1).unwrap();
 
         s.advance().ok();
 
-        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));
+        // TODO: server push
+        assert_eq!(s.poll_server(), Err(Error::Done));
     }
 
     #[test]
@@ -2537,31 +2634,68 @@
         let mut s = Session::default().unwrap();
         s.handshake().unwrap();
 
-        s.send_frame_server(
-            frame::Frame::GoAway { stream_id: 100 },
-            s.server.control_stream_id.unwrap(),
-            false,
-        )
-        .unwrap();
+        s.server.send_goaway(&mut s.pipe.server, 4000).unwrap();
 
-        assert_eq!(s.poll_client(), Err(Error::Done));
+        s.advance().ok();
+
+        assert_eq!(s.poll_client(), Ok((4000, Event::GoAway)));
     }
 
     #[test]
-    /// Send a GOAWAY frame from the server, that references a request that does
-    /// not exist.
-    fn goaway_from_server_bad_id() {
+    /// A client MUST NOT send a request after it receives GOAWAY.
+    fn client_request_after_goaway() {
+        let mut s = Session::default().unwrap();
+        s.handshake().unwrap();
+
+        s.server.send_goaway(&mut s.pipe.server, 4000).unwrap();
+
+        s.advance().ok();
+
+        assert_eq!(s.poll_client(), Ok((4000, Event::GoAway)));
+
+        assert_eq!(s.send_request(true), Err(Error::FrameUnexpected));
+    }
+
+    #[test]
+    /// Send a GOAWAY frame from the server, using an invalid goaway ID.
+    fn goaway_from_server_invalid_id() {
         let mut s = Session::default().unwrap();
         s.handshake().unwrap();
 
         s.send_frame_server(
-            frame::Frame::GoAway { stream_id: 1 },
+            frame::Frame::GoAway { id: 1 },
             s.server.control_stream_id.unwrap(),
             false,
         )
         .unwrap();
 
-        assert_eq!(s.poll_client(), Err(Error::FrameUnexpected));
+        assert_eq!(s.poll_client(), Err(Error::IdError));
+    }
+
+    #[test]
+    /// Send multiple GOAWAY frames from the server, that increase the goaway
+    /// ID.
+    fn goaway_from_server_increase_id() {
+        let mut s = Session::default().unwrap();
+        s.handshake().unwrap();
+
+        s.send_frame_server(
+            frame::Frame::GoAway { id: 0 },
+            s.server.control_stream_id.unwrap(),
+            false,
+        )
+        .unwrap();
+
+        s.send_frame_server(
+            frame::Frame::GoAway { id: 4 },
+            s.server.control_stream_id.unwrap(),
+            false,
+        )
+        .unwrap();
+
+        assert_eq!(s.poll_client(), Ok((0, Event::GoAway)));
+
+        assert_eq!(s.poll_client(), Err(Error::IdError));
     }
 
     #[test]
diff --git a/src/h3/stream.rs b/src/h3/stream.rs
index ece2e68..f2f8f0c 100644
--- a/src/h3/stream.rs
+++ b/src/h3/stream.rs
@@ -627,7 +627,7 @@
         let mut d = vec![42; 40];
         let mut b = octets::OctetsMut::with_slice(&mut d);
 
-        let goaway = frame::Frame::GoAway { stream_id: 0 };
+        let goaway = frame::Frame::GoAway { id: 0 };
 
         let settings = frame::Frame::Settings {
             max_header_list_size: Some(0),
diff --git a/tools/apps/src/lib.rs b/tools/apps/src/lib.rs
index 6de939b..6c1b706 100644
--- a/tools/apps/src/lib.rs
+++ b/tools/apps/src/lib.rs
@@ -852,6 +852,7 @@
     h3_conn: quiche::h3::Connection,
     reqs_sent: usize,
     reqs_complete: usize,
+    largest_processed_request: u64,
     reqs: Vec<Http3Request>,
     body: Option<Vec<u8>>,
     dump_json: bool,
@@ -926,6 +927,7 @@
             .unwrap(),
             reqs_sent: 0,
             reqs_complete: 0,
+            largest_processed_request: 0,
             reqs,
             body: body.as_ref().map(|b| b.to_vec()),
             dump_json,
@@ -946,6 +948,7 @@
             .unwrap(),
             reqs_sent: 0,
             reqs_complete: 0,
+            largest_processed_request: 0,
             reqs: Vec::new(),
             body: None,
             dump_json: false,
@@ -1236,6 +1239,14 @@
                     );
                 },
 
+                Ok((goaway_id, quiche::h3::Event::GoAway)) => {
+                    info!(
+                        "{} got GOAWAY with ID {} ",
+                        conn.trace_id(),
+                        goaway_id
+                    );
+                },
+
                 Err(quiche::h3::Error::Done) => {
                     break;
                 },
@@ -1281,6 +1292,9 @@
                         stream_id
                     );
 
+                    self.largest_processed_request =
+                        std::cmp::max(self.largest_processed_request, stream_id);
+
                     // We decide the response based on headers alone, so
                     // stop reading the request stream so that any body
                     // is ignored and pointless Data events are not
@@ -1369,6 +1383,16 @@
                     );
                 },
 
+                Ok((goaway_id, quiche::h3::Event::GoAway)) => {
+                    trace!(
+                        "{} got GOAWAY with ID {} ",
+                        conn.trace_id(),
+                        goaway_id
+                    );
+                    self.h3_conn
+                        .send_goaway(conn, self.largest_processed_request)?;
+                },
+
                 Err(quiche::h3::Error::Done) => {
                     break;
                 },
diff --git a/tools/http3_test/src/runner.rs b/tools/http3_test/src/runner.rs
index 6d589e9..7befde1 100644
--- a/tools/http3_test/src/runner.rs
+++ b/tools/http3_test/src/runner.rs
@@ -275,6 +275,8 @@
 
                     Ok((_flow_id, quiche::h3::Event::Datagram)) => (),
 
+                    Ok((_goaway_id, quiche::h3::Event::GoAway)) => (),
+
                     Err(quiche::h3::Error::Done) => {
                         break;
                     },