implement handshake timeout setting

Currently applications need to implement this on their own, but to avoid
needless duplication we can offer this functionality directly in quiche.
diff --git a/apps/src/args.rs b/apps/src/args.rs
index 24234a9..7000526 100644
--- a/apps/src/args.rs
+++ b/apps/src/args.rs
@@ -39,6 +39,7 @@
     pub max_stream_window: u64,
     pub max_streams_bidi: u64,
     pub max_streams_uni: u64,
+    pub handshake_timeout: u64,
     pub idle_timeout: u64,
     pub early_data: bool,
     pub dump_packet_path: Option<String>,
@@ -131,6 +132,9 @@
         let max_streams_uni = args.get_str("--max-streams-uni");
         let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();
 
+        let handshake_timeout = args.get_str("--handshake-timeout");
+        let handshake_timeout = handshake_timeout.parse::<u64>().unwrap();
+
         let idle_timeout = args.get_str("--idle-timeout");
         let idle_timeout = idle_timeout.parse::<u64>().unwrap();
 
@@ -199,6 +203,7 @@
             max_stream_window,
             max_streams_bidi,
             max_streams_uni,
+            handshake_timeout,
             idle_timeout,
             early_data,
             dump_packet_path,
@@ -228,6 +233,7 @@
             max_stream_window: 16777216,
             max_streams_bidi: 100,
             max_streams_uni: 100,
+            handshake_timeout: 5000,
             idle_timeout: 30000,
             early_data: false,
             dump_packet_path: None,
@@ -260,6 +266,7 @@
   --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].
   --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
   --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
+  --handshake-timeout TIMEOUT   Handshake timeout in milliseconds [default: 5000].
   --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
   --wire-version VERSION   The version number to send to the server [default: babababa].
   --http-version VERSION   HTTP version to use [default: all].
diff --git a/apps/src/bin/quiche-server.rs b/apps/src/bin/quiche-server.rs
index ac632dc..8e1cf7e 100644
--- a/apps/src/bin/quiche-server.rs
+++ b/apps/src/bin/quiche-server.rs
@@ -108,6 +108,8 @@
 
     config.set_application_protos(&conn_args.alpns).unwrap();
 
+    config.set_handshake_timeout(conn_args.handshake_timeout);
+
     config.set_max_idle_timeout(conn_args.idle_timeout);
     config.set_max_recv_udp_payload_size(max_datagram_size);
     config.set_max_send_udp_payload_size(max_datagram_size);
diff --git a/apps/src/client.rs b/apps/src/client.rs
index 86fb650..5fbe5af 100644
--- a/apps/src/client.rs
+++ b/apps/src/client.rs
@@ -116,6 +116,8 @@
 
     config.set_application_protos(&conn_args.alpns).unwrap();
 
+    config.set_handshake_timeout(conn_args.handshake_timeout);
+
     config.set_max_idle_timeout(conn_args.idle_timeout);
     config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
     config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
diff --git a/quiche/include/quiche.h b/quiche/include/quiche.h
index c0d1406..184d520 100644
--- a/quiche/include/quiche.h
+++ b/quiche/include/quiche.h
@@ -172,6 +172,9 @@
                                          const uint8_t *protos,
                                          size_t protos_len);
 
+// Sets the handshake timeout, in milliseconds, default is no timeout.
+void quiche_config_set_handshake_timeout(quiche_config *config, uint64_t v);
+
 // Sets the `max_idle_timeout` transport parameter, in milliseconds, default is
 // no timeout.
 void quiche_config_set_max_idle_timeout(quiche_config *config, uint64_t v);
diff --git a/quiche/src/ffi.rs b/quiche/src/ffi.rs
index 0183730..2b3b36d 100644
--- a/quiche/src/ffi.rs
+++ b/quiche/src/ffi.rs
@@ -237,6 +237,11 @@
 }
 
 #[no_mangle]
+pub extern fn quiche_config_set_handshake_timeout(config: &mut Config, v: u64) {
+    config.set_handshake_timeout(v);
+}
+
+#[no_mangle]
 pub extern fn quiche_config_set_max_idle_timeout(config: &mut Config, v: u64) {
     config.set_max_idle_timeout(v);
 }
diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs
index a6788c3..6783937 100644
--- a/quiche/src/lib.rs
+++ b/quiche/src/lib.rs
@@ -728,6 +728,8 @@
     max_connection_window: u64,
     max_stream_window: u64,
 
+    handshake_timeout: u64,
+
     disable_dcid_reuse: bool,
 }
 
@@ -793,6 +795,8 @@
             max_connection_window: MAX_CONNECTION_WINDOW,
             max_stream_window: stream::MAX_STREAM_WINDOW,
 
+            handshake_timeout: 0,
+
             disable_dcid_reuse: false,
         })
     }
@@ -972,6 +976,13 @@
         self.set_application_protos(&protos_list)
     }
 
+    /// Sets the timeout for the handshake.
+    ///
+    /// The default value is infinite, that is, no timeout is used.
+    pub fn set_handshake_timeout(&mut self, v: u64) {
+        self.handshake_timeout = v;
+    }
+
     /// Sets the `max_idle_timeout` transport parameter, in milliseconds.
     ///
     /// The default value is infinite, that is, no timeout is used.
@@ -1274,6 +1285,9 @@
     /// TLS handshake state.
     handshake: tls::Handshake,
 
+    /// The configured handshake timer deadline, if any is configured.
+    handshake_timer: Option<time::Instant>,
+
     /// Serialized TLS session buffer.
     ///
     /// This field is populated when a new session ticket is processed on the
@@ -1716,6 +1730,8 @@
         scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr,
         peer: SocketAddr, config: &Config, tls: tls::Handshake, is_server: bool,
     ) -> Result<Connection> {
+        let now = time::Instant::now();
+
         let max_rx_data = config.local_transport_params.initial_max_data;
 
         let scid_as_hex: Vec<String> =
@@ -1736,6 +1752,7 @@
             config.path_challenge_recv_max_queue_len,
             true,
         );
+
         // If we did stateless retry assume the peer's address is verified.
         path.verified_peer_address = odcid.is_some();
         // Assume clients validate the server's address implicitly.
@@ -1757,6 +1774,12 @@
             reset_token,
         );
 
+        let handshake_timer = if config.handshake_timeout > 0 {
+            Some(now + time::Duration::from_millis(config.handshake_timeout))
+        } else {
+            None
+        };
+
         let mut conn = Connection {
             version: config.version,
 
@@ -1776,6 +1799,8 @@
 
             handshake: tls,
 
+            handshake_timer,
+
             session: None,
 
             recovery_config,
@@ -5530,10 +5555,6 @@
             // processing the other timers.
             self.draining_timer
         } else {
-            // Use the lowest timer value (i.e. "sooner") among idle and loss
-            // detection timers. If they are both unset (i.e. `None`) then the
-            // result is `None`, but if at least one of them is set then a
-            // `Some(...)` value is returned.
             let path_timer = self
                 .paths
                 .iter()
@@ -5546,8 +5567,16 @@
                 .as_ref()
                 .map(|key_update| key_update.timer);
 
-            let timers = [self.idle_timer, path_timer, key_update_timer];
+            let timers = [
+                self.idle_timer,
+                self.handshake_timer,
+                path_timer,
+                key_update_timer,
+            ];
 
+            // Use the lowest timer value (i.e. "sooner"). If they are all unset
+            // (i.e. `None`) then the result is `None`, but if at least one of
+            // them is set then a `Some(...)` value is returned.
             timers.iter().filter_map(|&x| x).min()
         }
     }
@@ -5599,6 +5628,16 @@
             }
         }
 
+        if let Some(timer) = self.handshake_timer {
+            if timer <= now {
+                trace!("{} handshake timeout expired", self.trace_id);
+
+                self.mark_closed();
+                self.timed_out = true;
+                return;
+            }
+        }
+
         if let Some(timer) = self.pkt_num_spaces[packet::Epoch::Application]
             .key_update
             .as_ref()
@@ -6502,6 +6541,9 @@
                 self.drop_epoch_state(packet::Epoch::Handshake, now);
             }
 
+            // Disarm handshake timer.
+            self.handshake_timer = None;
+
             // Once the handshake is completed there's no point in processing
             // 0-RTT packets anymore, so clear the buffer now.
             self.undecryptable_pkts.clear();
@@ -8857,6 +8899,68 @@
     }
 
     #[test]
+    fn handshake_timeout_client() {
+        let mut config = Config::new(PROTOCOL_VERSION).unwrap();
+        config
+            .set_application_protos(&[b"proto1", b"proto2"])
+            .unwrap();
+        config.set_handshake_timeout(500);
+        config.verify_peer(false);
+
+        let mut pipe = testing::Pipe::with_client_config(&mut config).unwrap();
+
+        let timer = pipe.client.timeout().unwrap();
+        assert!(timer <= time::Duration::from_millis(500));
+
+        // Client sends initial flight.
+        let flight = testing::emit_flight(&mut pipe.client).unwrap();
+        testing::process_flight(&mut pipe.server, flight).unwrap();
+
+        std::thread::sleep(timer + time::Duration::from_millis(1));
+
+        pipe.client.on_timeout();
+        assert!(pipe.client.is_timed_out());
+
+        pipe.server.on_timeout();
+        assert!(!pipe.server.is_timed_out());
+    }
+
+    #[test]
+    fn handshake_timeout_server() {
+        let mut config = Config::new(PROTOCOL_VERSION).unwrap();
+        config
+            .load_cert_chain_from_pem_file("examples/cert.crt")
+            .unwrap();
+        config
+            .load_priv_key_from_pem_file("examples/cert.key")
+            .unwrap();
+        config
+            .set_application_protos(&[b"proto1", b"proto2"])
+            .unwrap();
+        config.set_handshake_timeout(500);
+
+        let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap();
+
+        let timer = pipe.server.timeout().unwrap();
+        assert!(timer <= time::Duration::from_millis(500));
+
+        // Client sends initial flight.
+        let flight = testing::emit_flight(&mut pipe.client).unwrap();
+        testing::process_flight(&mut pipe.server, flight).unwrap();
+
+        // Server sends initial flight.
+        let _ = testing::emit_flight(&mut pipe.server).unwrap();
+
+        std::thread::sleep(timer + time::Duration::from_millis(1));
+
+        pipe.client.on_timeout();
+        assert!(!pipe.client.is_timed_out());
+
+        pipe.server.on_timeout();
+        assert!(pipe.server.is_timed_out());
+    }
+
+    #[test]
     fn handshake_done() {
         let mut pipe = testing::Pipe::new().unwrap();