gfe-relnote: In QUIC version T099, implement HANDSHAKE_DONE frame to drive the handshake to confirmation on the client side. Not used in prod yet.

PiperOrigin-RevId: 290948924
Change-Id: Idcbc5c0d573b5db992b65d4971ea12a3d7e87633
diff --git a/quic/core/chlo_extractor.cc b/quic/core/chlo_extractor.cc
index d2a0608..496f201 100644
--- a/quic/core/chlo_extractor.cc
+++ b/quic/core/chlo_extractor.cc
@@ -73,6 +73,7 @@
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override;
   bool OnPaddingFrame(const QuicPaddingFrame& frame) override;
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
+  bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   void OnPacketComplete() override {}
   bool IsValidStatelessResetToken(QuicUint128 token) const override;
   void OnAuthenticatedIetfStatelessResetPacket(
@@ -280,6 +281,11 @@
   return true;
 }
 
+bool ChloFramerVisitor::OnHandshakeDoneFrame(
+    const QuicHandshakeDoneFrame& /*frame*/) {
+  return true;
+}
+
 bool ChloFramerVisitor::IsValidStatelessResetToken(
     QuicUint128 /*token*/) const {
   return false;
diff --git a/quic/core/frames/quic_frame.cc b/quic/core/frames/quic_frame.cc
index 44d3cc8..d40202f 100644
--- a/quic/core/frames/quic_frame.cc
+++ b/quic/core/frames/quic_frame.cc
@@ -19,6 +19,9 @@
 QuicFrame::QuicFrame(QuicStreamFrame stream_frame)
     : stream_frame(stream_frame) {}
 
+QuicFrame::QuicFrame(QuicHandshakeDoneFrame handshake_done_frame)
+    : handshake_done_frame(handshake_done_frame) {}
+
 QuicFrame::QuicFrame(QuicCryptoFrame* crypto_frame)
     : type(CRYPTO_FRAME), crypto_frame(crypto_frame) {}
 
@@ -89,6 +92,7 @@
     case STOP_WAITING_FRAME:
     case STREAMS_BLOCKED_FRAME:
     case STREAM_FRAME:
+    case HANDSHAKE_DONE_FRAME:
       break;
     case ACK_FRAME:
       delete frame->ack_frame;
@@ -159,6 +163,7 @@
     case MAX_STREAMS_FRAME:
     case PING_FRAME:
     case STOP_SENDING_FRAME:
+    case HANDSHAKE_DONE_FRAME:
       return true;
     default:
       return false;
@@ -183,6 +188,8 @@
       return frame.ping_frame.control_frame_id;
     case STOP_SENDING_FRAME:
       return frame.stop_sending_frame->control_frame_id;
+    case HANDSHAKE_DONE_FRAME:
+      return frame.handshake_done_frame.control_frame_id;
     default:
       return kInvalidControlFrameId;
   }
@@ -214,6 +221,9 @@
     case STOP_SENDING_FRAME:
       frame->stop_sending_frame->control_frame_id = control_frame_id;
       return;
+    case HANDSHAKE_DONE_FRAME:
+      frame->handshake_done_frame.control_frame_id = control_frame_id;
+      return;
     default:
       QUIC_BUG
           << "Try to set control frame id of a frame without control frame id";
@@ -247,6 +257,10 @@
     case MAX_STREAMS_FRAME:
       copy = QuicFrame(QuicMaxStreamsFrame(frame.max_streams_frame));
       break;
+    case HANDSHAKE_DONE_FRAME:
+      copy = QuicFrame(
+          QuicHandshakeDoneFrame(frame.handshake_done_frame.control_frame_id));
+      break;
     default:
       QUIC_BUG << "Try to copy a non-retransmittable control frame: " << frame;
       copy = QuicFrame(QuicPingFrame(kInvalidControlFrameId));
@@ -334,6 +348,10 @@
       copy = QuicFrame(
           new QuicRetireConnectionIdFrame(*frame.retire_connection_id_frame));
       break;
+    case HANDSHAKE_DONE_FRAME:
+      copy = QuicFrame(
+          QuicHandshakeDoneFrame(frame.handshake_done_frame.control_frame_id));
+      break;
     default:
       QUIC_BUG << "Cannot copy frame: " << frame;
       copy = QuicFrame(QuicPingFrame(kInvalidControlFrameId));
@@ -430,6 +448,9 @@
     case NEW_TOKEN_FRAME:
       os << "type { NEW_TOKEN_FRAME }" << *(frame.new_token_frame);
       break;
+    case HANDSHAKE_DONE_FRAME:
+      os << "type { HANDSHAKE_DONE_FRAME } " << frame.handshake_done_frame;
+      break;
     default: {
       QUIC_LOG(ERROR) << "Unknown frame type: " << frame.type;
       break;
diff --git a/quic/core/frames/quic_frame.h b/quic/core/frames/quic_frame.h
index 226cbfb..756b69f 100644
--- a/quic/core/frames/quic_frame.h
+++ b/quic/core/frames/quic_frame.h
@@ -13,6 +13,7 @@
 #include "net/third_party/quiche/src/quic/core/frames/quic_connection_close_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_crypto_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_goaway_frame.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_handshake_done_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_max_streams_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_message_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_mtu_discovery_frame.h"
@@ -45,6 +46,7 @@
   explicit QuicFrame(QuicStopWaitingFrame frame);
   explicit QuicFrame(QuicStreamsBlockedFrame frame);
   explicit QuicFrame(QuicStreamFrame stream_frame);
+  explicit QuicFrame(QuicHandshakeDoneFrame handshake_done_frame);
 
   explicit QuicFrame(QuicAckFrame* frame);
   explicit QuicFrame(QuicRstStreamFrame* frame);
@@ -76,6 +78,7 @@
     QuicStopWaitingFrame stop_waiting_frame;
     QuicStreamsBlockedFrame streams_blocked_frame;
     QuicStreamFrame stream_frame;
+    QuicHandshakeDoneFrame handshake_done_frame;
 
     // Out of line frames.
     struct {
diff --git a/quic/core/frames/quic_frames_test.cc b/quic/core/frames/quic_frames_test.cc
index 1764e17..7450adb 100644
--- a/quic/core/frames/quic_frames_test.cc
+++ b/quic/core/frames/quic_frames_test.cc
@@ -234,6 +234,17 @@
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
+TEST_F(QuicFramesTest, HandshakeDoneFrameToString) {
+  QuicHandshakeDoneFrame handshake_done;
+  QuicFrame frame(handshake_done);
+  SetControlFrameId(6, &frame);
+  EXPECT_EQ(6u, GetControlFrameId(frame));
+  std::ostringstream stream;
+  stream << frame.handshake_done_frame;
+  EXPECT_EQ("{ control_frame_id: 6 }\n", stream.str());
+  EXPECT_TRUE(IsControlFrame(frame.type));
+}
+
 TEST_F(QuicFramesTest, StreamFrameToString) {
   QuicStreamFrame frame;
   frame.stream_id = 1;
@@ -546,6 +557,9 @@
       case RETIRE_CONNECTION_ID_FRAME:
         frames.push_back(QuicFrame(new QuicRetireConnectionIdFrame()));
         break;
+      case HANDSHAKE_DONE_FRAME:
+        frames.push_back(QuicFrame(QuicHandshakeDoneFrame()));
+        break;
       default:
         ASSERT_TRUE(false)
             << "Please fix CopyQuicFrames if a new frame type is added.";
diff --git a/quic/core/frames/quic_handshake_done_frame.cc b/quic/core/frames/quic_handshake_done_frame.cc
new file mode 100644
index 0000000..6f411a5
--- /dev/null
+++ b/quic/core/frames/quic_handshake_done_frame.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_handshake_done_frame.h"
+
+namespace quic {
+
+QuicHandshakeDoneFrame::QuicHandshakeDoneFrame()
+    : QuicInlinedFrame(HANDSHAKE_DONE_FRAME),
+      control_frame_id(kInvalidControlFrameId) {}
+
+QuicHandshakeDoneFrame::QuicHandshakeDoneFrame(
+    QuicControlFrameId control_frame_id)
+    : QuicInlinedFrame(HANDSHAKE_DONE_FRAME),
+      control_frame_id(control_frame_id) {}
+
+std::ostream& operator<<(std::ostream& os,
+                         const QuicHandshakeDoneFrame& handshake_done_frame) {
+  os << "{ control_frame_id: " << handshake_done_frame.control_frame_id
+     << " }\n";
+  return os;
+}
+
+}  // namespace quic
diff --git a/quic/core/frames/quic_handshake_done_frame.h b/quic/core/frames/quic_handshake_done_frame.h
new file mode 100644
index 0000000..48aa3c7
--- /dev/null
+++ b/quic/core/frames/quic_handshake_done_frame.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_HANDSHAKE_DONE_FRAME_H_
+#define QUICHE_QUIC_CORE_FRAMES_QUIC_HANDSHAKE_DONE_FRAME_H_
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_inlined_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// A HANDSHAKE_DONE frame contains no payload, and it is retransmittable,
+// and ACK'd just like other normal frames.
+struct QUIC_EXPORT_PRIVATE QuicHandshakeDoneFrame
+    : public QuicInlinedFrame<QuicHandshakeDoneFrame> {
+  QuicHandshakeDoneFrame();
+  explicit QuicHandshakeDoneFrame(QuicControlFrameId control_frame_id);
+
+  friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+      std::ostream& os,
+      const QuicHandshakeDoneFrame& handshake_done_frame);
+
+  // A unique identifier of this control frame. 0 when this frame is received,
+  // and non-zero when sent.
+  QuicControlFrameId control_frame_id;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_FRAMES_QUIC_HANDSHAKE_DONE_FRAME_H_
diff --git a/quic/core/handshaker_delegate_interface.h b/quic/core/handshaker_delegate_interface.h
index 9eae32a..23f2cbd 100644
--- a/quic/core/handshaker_delegate_interface.h
+++ b/quic/core/handshaker_delegate_interface.h
@@ -38,13 +38,10 @@
   // Called to neuter ENCRYPTION_INITIAL data (without discarding initial keys).
   virtual void NeuterUnencryptedData() = 0;
 
-  // Called to neuter data of HANDSHAKE_DATA packet number space. In QUIC
-  // crypto, this is called 1) when a client switches to forward secure
+  // Called to neuter data of HANDSHAKE_DATA packet number space. Only used in
+  // QUIC crypto. This is called 1) when a client switches to forward secure
   // encryption level and 2) a server successfully processes a forward secure
-  // packet. Temporarily use this method in TLS handshake when both endpoints
-  // switch to forward secure encryption level.
-  // TODO(fayang): use DiscardOldEncryptionKey instead of this method in TLS
-  // handshake when handshake key discarding settles down.
+  // packet.
   virtual void NeuterHandshakeData() = 0;
 };
 
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 15ef0f9..080530d 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -679,6 +679,40 @@
   EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
 }
 
+class HandshakeDoneObserver : public QuicConnectionDebugVisitor {
+ public:
+  HandshakeDoneObserver() : handshake_done_received_(false) {}
+
+  size_t handshake_done_received() const { return handshake_done_received_; }
+
+  void OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& /*frame*/) override {
+    handshake_done_received_ = true;
+  }
+
+ private:
+  bool handshake_done_received_;
+};
+
+TEST_P(EndToEndTestWithTls, HandshakeDone) {
+  ASSERT_TRUE(Initialize());
+  if (!GetParam().negotiated_version.HasHandshakeDone()) {
+    return;
+  }
+  HandshakeDoneObserver observer;
+  QuicConnection* client_connection = GetClientConnection();
+  client_connection->set_debug_visitor(&observer);
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  // Verify handshake state.
+  EXPECT_EQ(HANDSHAKE_CONFIRMED, GetClientSession()->GetHandshakeState());
+  server_thread_->Pause();
+  EXPECT_EQ(HANDSHAKE_CONFIRMED, GetServerSession()->GetHandshakeState());
+  server_thread_->Resume();
+  client_->Disconnect();
+  // Verify handshake done frame has been receveid by client.
+  EXPECT_TRUE(observer.handshake_done_received());
+}
+
 TEST_P(EndToEndTestWithTls, SendAndReceiveCoalescedPackets) {
   ASSERT_TRUE(Initialize());
   if (!GetClientConnection()->version().CanSendCoalescedPackets()) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index a4c1af0..1522118 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -131,6 +131,7 @@
     return QuicCryptoHandshaker::crypto_message_parser();
   }
   void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
+  void OnHandshakeDoneReceived() override {}
 
   MOCK_METHOD0(OnCanWrite, void());
 
@@ -479,6 +480,9 @@
         .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
   }
   EXPECT_FALSE(session_.OneRttKeysAvailable());
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+  }
   CryptoHandshakeMessage message;
   session_.GetMutableCryptoStream()->OnHandshakeMessage(message);
   EXPECT_TRUE(session_.OneRttKeysAvailable());
@@ -742,7 +746,10 @@
         .WillRepeatedly(Invoke(
             this, &QuicSpdySessionTestServer::ClearMaxStreamsControlFrame));
   }
-
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillRepeatedly(Invoke(&ClearControlFrame));
+  }
   // Encryption needs to be established before data can be sent.
   CryptoHandshakeMessage msg;
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
@@ -1084,6 +1091,9 @@
   }
   EXPECT_EQ(kInitialIdleTimeoutSecs + 3,
             QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+  }
   CryptoHandshakeMessage msg;
   session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
   EXPECT_EQ(kMaximumIdleTimeoutSecs + 3,
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 6ef2db8..7ba472a 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1360,6 +1360,28 @@
   return connected_;
 }
 
+bool QuicConnection::OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) {
+  DCHECK(connected_ && VersionHasIetfQuicFrames(transport_version()));
+
+  if (perspective_ == Perspective::IS_SERVER) {
+    CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION,
+                    "Server received handshake done frame.",
+                    ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+
+  // Since a handshake done frame was received, this is not a connectivity
+  // probe. A probe only contains a PING and full padding.
+  UpdatePacketContent(NOT_PADDED_PING);
+
+  if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnHandshakeDoneFrame(frame);
+  }
+  visitor_->OnHandshakeDoneReceived();
+  should_last_packet_instigate_acks_ = true;
+  return connected_;
+}
+
 bool QuicConnection::OnBlockedFrame(const QuicBlockedFrame& frame) {
   DCHECK(connected_);
 
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index b111f3c..fb38d97 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -89,6 +89,9 @@
   // Called when |message| has been received.
   virtual void OnMessageReceived(quiche::QuicheStringPiece message) = 0;
 
+  // Called when a HANDSHAKE_DONE frame has been received.
+  virtual void OnHandshakeDoneReceived() = 0;
+
   // Called when a MAX_STREAMS frame has been received from the peer.
   virtual bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) = 0;
 
@@ -261,6 +264,9 @@
   // Called when a MessageFrame has been parsed.
   virtual void OnMessageFrame(const QuicMessageFrame& /*frame*/) {}
 
+  // Called when a HandshakeDoneFrame has been parsed.
+  virtual void OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& /*frame*/) {}
+
   // Called when a public reset packet has been received.
   virtual void OnPublicResetPacket(const QuicPublicResetPacket& /*packet*/) {}
 
@@ -516,6 +522,7 @@
       const QuicRetireConnectionIdFrame& frame) override;
   bool OnNewTokenFrame(const QuicNewTokenFrame& frame) override;
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
+  bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override;
   void OnPacketComplete() override;
   bool IsValidStatelessResetToken(QuicUint128 token) const override;
   void OnAuthenticatedIetfStatelessResetPacket(
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 8dd8f67..9441e86 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -1191,6 +1191,14 @@
   size_t ProcessFramePacketAtLevel(uint64_t number,
                                    QuicFrame frame,
                                    EncryptionLevel level) {
+    QuicFrames frames;
+    frames.push_back(frame);
+    return ProcessFramesPacketAtLevel(number, frames, level);
+  }
+
+  size_t ProcessFramesPacketAtLevel(uint64_t number,
+                                    const QuicFrames& frames,
+                                    EncryptionLevel level) {
     QuicPacketHeader header;
     header.destination_connection_id = connection_id_;
     header.packet_number_length = packet_number_length_;
@@ -1212,8 +1220,6 @@
       header.source_connection_id_included = CONNECTION_ID_PRESENT;
     }
     header.packet_number = QuicPacketNumber(number);
-    QuicFrames frames;
-    frames.push_back(frame);
     std::unique_ptr<QuicPacket> packet(ConstructPacket(header, frames));
     // Set the correct encryption level and encrypter on peer_creator and
     // peer_framer, respectively.
@@ -9691,6 +9697,34 @@
   EXPECT_NE(nullptr, writer_->coalesced_packet());
 }
 
+TEST_P(QuicConnectionTest, ClientReceivedHandshakeDone) {
+  if (!connection_.version().HasHandshakeDone()) {
+    return;
+  }
+  EXPECT_CALL(visitor_, OnHandshakeDoneReceived());
+  QuicFrames frames;
+  frames.push_back(QuicFrame(QuicHandshakeDoneFrame()));
+  frames.push_back(QuicFrame(QuicPaddingFrame(-1)));
+  ProcessFramesPacketAtLevel(1, frames, ENCRYPTION_FORWARD_SECURE);
+}
+
+TEST_P(QuicConnectionTest, ServerReceivedHandshakeDone) {
+  if (!connection_.version().HasHandshakeDone()) {
+    return;
+  }
+  set_perspective(Perspective::IS_SERVER);
+  EXPECT_CALL(visitor_, OnHandshakeDoneReceived()).Times(0);
+  EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+      .WillOnce(Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame));
+  QuicFrames frames;
+  frames.push_back(QuicFrame(QuicHandshakeDoneFrame()));
+  frames.push_back(QuicFrame(QuicPaddingFrame(-1)));
+  ProcessFramesPacketAtLevel(1, frames, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_EQ(1, connection_close_frame_count_);
+  EXPECT_THAT(saved_connection_close_frame_.quic_error_code,
+              IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_control_frame_manager.cc b/quic/core/quic_control_frame_manager.cc
index de49366..5e33368 100644
--- a/quic/core/quic_control_frame_manager.cc
+++ b/quic/core/quic_control_frame_manager.cc
@@ -110,6 +110,12 @@
       new QuicStopSendingFrame(++last_control_frame_id_, stream_id, code)));
 }
 
+void QuicControlFrameManager::WriteOrBufferHandshakeDone() {
+  QUIC_DVLOG(1) << "Writing HANDSHAKE_DONE";
+  WriteOrBufferQuicFrame(
+      QuicFrame(QuicHandshakeDoneFrame(++last_control_frame_id_)));
+}
+
 void QuicControlFrameManager::WritePing() {
   QUIC_DVLOG(1) << "Writing PING_FRAME";
   if (HasBufferedFrames()) {
diff --git a/quic/core/quic_control_frame_manager.h b/quic/core/quic_control_frame_manager.h
index 61dfba1..ac31a1e 100644
--- a/quic/core/quic_control_frame_manager.h
+++ b/quic/core/quic_control_frame_manager.h
@@ -67,6 +67,10 @@
   // can not be sent immediately.
   void WriteOrBufferStopSending(uint16_t code, QuicStreamId stream_id);
 
+  // Tries to send an HANDSHAKE_DONE frame. The frame is buffered if it can not
+  // be sent immediately.
+  void WriteOrBufferHandshakeDone();
+
   // Sends a PING_FRAME. Do not send PING if there is buffered frames.
   void WritePing();
 
diff --git a/quic/core/quic_crypto_client_handshaker.cc b/quic/core/quic_crypto_client_handshaker.cc
index 795f78c..982d070 100644
--- a/quic/core/quic_crypto_client_handshaker.cc
+++ b/quic/core/quic_crypto_client_handshaker.cc
@@ -153,6 +153,10 @@
   return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START;
 }
 
+void QuicCryptoClientHandshaker::OnHandshakeDoneReceived() {
+  DCHECK(false);
+}
+
 size_t QuicCryptoClientHandshaker::BufferSizeLimitForLevel(
     EncryptionLevel level) const {
   return QuicCryptoHandshaker::BufferSizeLimitForLevel(level);
diff --git a/quic/core/quic_crypto_client_handshaker.h b/quic/core/quic_crypto_client_handshaker.h
index 9729b1d..dc799fb 100644
--- a/quic/core/quic_crypto_client_handshaker.h
+++ b/quic/core/quic_crypto_client_handshaker.h
@@ -47,6 +47,7 @@
   CryptoMessageParser* crypto_message_parser() override;
   HandshakeState GetHandshakeState() const override;
   size_t BufferSizeLimitForLevel(EncryptionLevel level) const override;
+  void OnHandshakeDoneReceived() override;
 
   // From QuicCryptoHandshaker
   void OnHandshakeMessage(const CryptoHandshakeMessage& message) override;
diff --git a/quic/core/quic_crypto_client_stream.cc b/quic/core/quic_crypto_client_stream.cc
index d681eb3..92d396f 100644
--- a/quic/core/quic_crypto_client_stream.cc
+++ b/quic/core/quic_crypto_client_stream.cc
@@ -99,4 +99,8 @@
   return handshaker_->chlo_hash();
 }
 
+void QuicCryptoClientStream::OnHandshakeDoneReceived() {
+  handshaker_->OnHandshakeDoneReceived();
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_crypto_client_stream.h b/quic/core/quic_crypto_client_stream.h
index 76d0301..fd81259 100644
--- a/quic/core/quic_crypto_client_stream.h
+++ b/quic/core/quic_crypto_client_stream.h
@@ -120,6 +120,9 @@
 
     // Returns current handshake state.
     virtual HandshakeState GetHandshakeState() const = 0;
+
+    // Called when handshake done has been received.
+    virtual void OnHandshakeDoneReceived() = 0;
   };
 
   // ProofHandler is an interface that handles callbacks from the crypto
@@ -165,6 +168,7 @@
       const override;
   CryptoMessageParser* crypto_message_parser() override;
   void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
+  void OnHandshakeDoneReceived() override;
   HandshakeState GetHandshakeState() const override;
   size_t BufferSizeLimitForLevel(EncryptionLevel level) const override;
 
diff --git a/quic/core/quic_crypto_server_stream.cc b/quic/core/quic_crypto_server_stream.cc
index f4a514d..059317e 100644
--- a/quic/core/quic_crypto_server_stream.cc
+++ b/quic/core/quic_crypto_server_stream.cc
@@ -165,6 +165,10 @@
   handshaker_->OnPacketDecrypted(level);
 }
 
+void QuicCryptoServerStream::OnHandshakeDoneReceived() {
+  DCHECK(false);
+}
+
 HandshakeState QuicCryptoServerStream::GetHandshakeState() const {
   return handshaker_->GetHandshakeState();
 }
diff --git a/quic/core/quic_crypto_server_stream.h b/quic/core/quic_crypto_server_stream.h
index db95116..9f8d08d 100644
--- a/quic/core/quic_crypto_server_stream.h
+++ b/quic/core/quic_crypto_server_stream.h
@@ -175,6 +175,7 @@
       const override;
   CryptoMessageParser* crypto_message_parser() override;
   void OnPacketDecrypted(EncryptionLevel level) override;
+  void OnHandshakeDoneReceived() override;
   HandshakeState GetHandshakeState() const override;
   size_t BufferSizeLimitForLevel(EncryptionLevel level) const override;
   void OnSuccessfulVersionNegotiation(
diff --git a/quic/core/quic_crypto_stream.h b/quic/core/quic_crypto_stream.h
index 7b9414c..66bd461 100644
--- a/quic/core/quic_crypto_stream.h
+++ b/quic/core/quic_crypto_stream.h
@@ -85,6 +85,9 @@
   // Called when a packet of encryption |level| has been successfully decrypted.
   virtual void OnPacketDecrypted(EncryptionLevel level) = 0;
 
+  // Called when a handshake done frame has been received.
+  virtual void OnHandshakeDoneReceived() = 0;
+
   // Returns current handshake state.
   virtual HandshakeState GetHandshakeState() const = 0;
 
diff --git a/quic/core/quic_crypto_stream_test.cc b/quic/core/quic_crypto_stream_test.cc
index 1b391ab..dfbc6ef 100644
--- a/quic/core/quic_crypto_stream_test.cc
+++ b/quic/core/quic_crypto_stream_test.cc
@@ -57,6 +57,7 @@
     return QuicCryptoHandshaker::crypto_message_parser();
   }
   void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
+  void OnHandshakeDoneReceived() override {}
   HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; }
 
  private:
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index bb048e1..ab99f05 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -655,6 +655,9 @@
       return GetPathChallengeFrameSize(*frame.path_challenge_frame);
     case STOP_SENDING_FRAME:
       return GetStopSendingFrameSize(*frame.stop_sending_frame);
+    case HANDSHAKE_DONE_FRAME:
+      // HANDSHAKE_DONE has no payload.
+      return kQuicFrameTypeSize;
 
     case STREAM_FRAME:
     case ACK_FRAME:
@@ -1158,6 +1161,9 @@
           return 0;
         }
         break;
+      case HANDSHAKE_DONE_FRAME:
+        // HANDSHAKE_DONE has no payload.
+        break;
       default:
         set_detailed_error("Tried to append unknown frame type.");
         RaiseError(QUIC_INVALID_FRAME_DATA);
@@ -3248,6 +3254,19 @@
           }
           break;
         }
+        case IETF_HANDSHAKE_DONE: {
+          // HANDSHAKE_DONE has no payload.
+          QuicHandshakeDoneFrame handshake_done_frame;
+          if (!visitor_->OnHandshakeDoneFrame(handshake_done_frame)) {
+            QUIC_DVLOG(1) << ENDPOINT
+                          << "Visitor asked to stop further processing.";
+            // Returning true since there was no parsing error.
+            return true;
+          }
+          QUIC_DVLOG(2) << ENDPOINT << "Processing handshake done frame "
+                        << handshake_done_frame;
+          break;
+        }
 
         default:
           set_detailed_error("Illegal frame type.");
@@ -4820,6 +4839,9 @@
     case CRYPTO_FRAME:
       type_byte = IETF_CRYPTO;
       break;
+    case HANDSHAKE_DONE_FRAME:
+      type_byte = IETF_HANDSHAKE_DONE;
+      break;
     default:
       QUIC_BUG << "Attempt to generate a frame type for an unsupported value: "
                << frame.type;
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 768aa33..1a83989 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -204,6 +204,9 @@
   // Called when a message frame has been parsed.
   virtual bool OnMessageFrame(const QuicMessageFrame& frame) = 0;
 
+  // Called when a handshake done frame has been parsed.
+  virtual bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) = 0;
+
   // Called when a packet has been completely processed.
   virtual void OnPacketComplete() = 0;
 
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index b794cb1..c51c9c2 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -389,6 +389,15 @@
     return true;
   }
 
+  bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override {
+    ++frame_count_;
+    handshake_done_frames_.push_back(
+        std::make_unique<QuicHandshakeDoneFrame>(frame));
+    DCHECK(VersionHasIetfQuicFrames(transport_version_));
+    EXPECT_EQ(IETF_HANDSHAKE_DONE, framer_->current_received_frame_type());
+    return true;
+  }
+
   void OnPacketComplete() override { ++complete_packets_; }
 
   bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override {
@@ -553,6 +562,7 @@
   std::vector<std::unique_ptr<QuicPaddingFrame>> padding_frames_;
   std::vector<std::unique_ptr<QuicPingFrame>> ping_frames_;
   std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_;
+  std::vector<std::unique_ptr<QuicHandshakeDoneFrame>> handshake_done_frames_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> undecryptable_packets_;
   std::vector<EncryptionLevel> undecryptable_decryption_levels_;
@@ -5104,6 +5114,39 @@
   // No need to check the PING frame boundaries because it has no payload.
 }
 
+TEST_P(QuicFramerTest, HandshakeDoneFrame) {
+  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+  // clang-format off
+  unsigned char packet[] = {
+     // type (short header, 4 byte packet number)
+     0x43,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // frame type (Handshake done frame)
+     0x1e,
+    };
+  // clang-format on
+
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUICHE_ARRAYSIZE(packet),
+                                false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_THAT(framer_.error(), IsQuicNoError());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(1u, visitor_.handshake_done_frames_.size());
+}
+
 TEST_P(QuicFramerTest, MessageFrame) {
   if (!VersionSupportsMessageFrames(framer_.transport_version())) {
     return;
@@ -8497,6 +8540,40 @@
                                                     : QUICHE_ARRAYSIZE(packet));
 }
 
+TEST_P(QuicFramerTest, BuildHandshakeDonePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicHandshakeDoneFrame())};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (Handshake done frame)
+    0x1e,
+  };
+  // clang-format on
+  if (!VersionHasIetfQuicFrames(framer_.transport_version())) {
+    return;
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  quiche::test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(packet),
+      QUICHE_ARRAYSIZE(packet));
+}
+
 TEST_P(QuicFramerTest, BuildMessagePacket) {
   if (!VersionSupportsMessageFrames(framer_.transport_version())) {
     return;
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 16ec7b6..74931ad 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -374,6 +374,11 @@
                 << ", " << message;
 }
 
+void QuicSession::OnHandshakeDoneReceived() {
+  QUIC_DVLOG(1) << ENDPOINT << "OnHandshakeDoneReceived";
+  GetMutableCryptoStream()->OnHandshakeDoneReceived();
+}
+
 // static
 void QuicSession::RecordConnectionCloseAtServer(QuicErrorCode error,
                                                 ConnectionCloseSource source) {
@@ -1372,6 +1377,12 @@
       }
       QUIC_BUG_IF(!config_.negotiated())
           << ENDPOINT << "Handshake confirmed without parameter negotiation.";
+      if (connection()->version().HasHandshakeDone() &&
+          perspective_ == Perspective::IS_SERVER) {
+        // Server sends HANDSHAKE_DONE to signal confirmation of the handshake
+        // to the client.
+        control_frame_manager_.WriteOrBufferHandshakeDone();
+      }
       break;
     default:
       QUIC_BUG << "Unknown encryption level: "
@@ -1398,9 +1409,7 @@
       NeuterUnencryptedData();
       break;
     case ENCRYPTION_HANDSHAKE:
-      DCHECK(false);
-      // TODO(fayang): implement this when handshake keys discarding settles
-      // down.
+      NeuterHandshakeData();
       break;
     case ENCRYPTION_ZERO_RTT:
       break;
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 84ba8e1..adeabb1 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -107,6 +107,7 @@
   void OnRstStream(const QuicRstStreamFrame& frame) override;
   void OnGoAway(const QuicGoAwayFrame& frame) override;
   void OnMessageReceived(quiche::QuicheStringPiece message) override;
+  void OnHandshakeDoneReceived() override;
   void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   void OnBlockedFrame(const QuicBlockedFrame& frame) override;
   void OnConnectionClosed(const QuicConnectionCloseFrame& frame,
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 007f355..d4bddf2 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -115,6 +115,7 @@
     return QuicCryptoHandshaker::crypto_message_parser();
   }
   void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
+  void OnHandshakeDoneReceived() override {}
   HandshakeState GetHandshakeState() const override {
     return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START;
   }
@@ -573,6 +574,9 @@
 TEST_P(QuicSessionTestServer, OneRttKeysAvailable) {
   EXPECT_FALSE(session_.OneRttKeysAvailable());
   CryptoHandshakeMessage message;
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+  }
   session_.GetMutableCryptoStream()->OnHandshakeMessage(message);
   EXPECT_TRUE(session_.OneRttKeysAvailable());
 }
@@ -1097,6 +1101,10 @@
 
 TEST_P(QuicSessionTestServer, OnCanWriteBundlesStreams) {
   // Encryption needs to be established before data can be sent.
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillRepeatedly(Invoke(&ClearControlFrame));
+  }
   CryptoHandshakeMessage msg;
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
@@ -1439,6 +1447,9 @@
 TEST_P(QuicSessionTestServer, IncreasedTimeoutAfterCryptoHandshake) {
   EXPECT_EQ(kInitialIdleTimeoutSecs + 3,
             QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+  }
   CryptoHandshakeMessage msg;
   session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
   EXPECT_EQ(kMaximumIdleTimeoutSecs + 3,
@@ -2354,6 +2365,9 @@
                          "", &storage)));
 
   // Finish handshake.
+  if (connection_->version().HasHandshakeDone()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+  }
   CryptoHandshakeMessage handshake_message;
   session_.GetMutableCryptoStream()->OnHandshakeMessage(handshake_message);
   EXPECT_TRUE(session_.OneRttKeysAvailable());
diff --git a/quic/core/quic_trace_visitor.cc b/quic/core/quic_trace_visitor.cc
index 5097485..9d18843 100644
--- a/quic/core/quic_trace_visitor.cc
+++ b/quic/core/quic_trace_visitor.cc
@@ -62,6 +62,7 @@
       case WINDOW_UPDATE_FRAME:
       case BLOCKED_FRAME:
       case PING_FRAME:
+      case HANDSHAKE_DONE_FRAME:
         PopulateFrameInfo(frame, event->add_frames());
         break;
 
@@ -193,6 +194,7 @@
 
     case PING_FRAME:
     case MTU_DISCOVERY_FRAME:
+    case HANDSHAKE_DONE_FRAME:
       frame_record->set_frame_type(quic_trace::PING);
       break;
 
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index d333fe5..42dc050 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -220,6 +220,7 @@
   MESSAGE_FRAME,
   NEW_TOKEN_FRAME,
   RETIRE_CONNECTION_ID_FRAME,
+  HANDSHAKE_DONE_FRAME,
 
   NUM_FRAME_TYPES
 };
@@ -270,6 +271,8 @@
   IETF_CONNECTION_CLOSE = 0x1c,
   IETF_APPLICATION_CLOSE = 0x1d,
 
+  IETF_HANDSHAKE_DONE = 0x1e,
+
   // The MESSAGE frame type has not yet been fully standardized.
   // QUIC versions starting with 46 and before 99 use 0x20-0x21.
   // IETF QUIC (v99) uses 0x30-0x31, see draft-pauly-quic-datagram.
@@ -729,13 +732,14 @@
   // In QUIC crypto, state proceeds to HANDSHAKE_COMPLETE if client receives
   // SHLO or server successfully processes an ENCRYPTION_FORWARD_SECURE
   // packet, such that the handshake packets can be neutered. In IETF QUIC
-  // with TLS handshake, state proceeds to HANDSHAKE_COMPLETE once the
-  // endpoint has both 1-RTT send and receive keys.
+  // with TLS handshake, state proceeds to HANDSHAKE_COMPLETE once the client
+  // has both 1-RTT send and receive keys.
   HANDSHAKE_COMPLETE,
   // Only used in IETF QUIC with TLS handshake. State proceeds to
-  // HANDSHAKE_CONFIRMED if a 1-RTT packet gets acknowledged.
-  // TODO(fayang): implement HANDSHAKE_DONE frame to drive handshake to
-  // confirmation according to https://github.com/quicwg/base-drafts/pull/3145.
+  // HANDSHAKE_CONFIRMED if a client receives HANDSHAKE_DONE frame or server has
+  // 1-RTT send and receive keys.
+  // TODO(fayang): on the client side, proceed state to HANDSHAKE_CONFIRMED once
+  // 1-RTT packet gets acknowledged..
   HANDSHAKE_CONFIRMED,
 };
 
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 72fd964..96f0a82 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -145,6 +145,11 @@
   return VersionHasIetfQuicFrames(transport_version);
 }
 
+bool ParsedQuicVersion::HasHandshakeDone() const {
+  DCHECK(IsKnown());
+  return HasIetfQuicFrames() && handshake_protocol == PROTOCOL_TLS1_3;
+}
+
 bool VersionHasLengthPrefixedConnectionIds(
     QuicTransportVersion transport_version) {
   DCHECK(transport_version != QUIC_VERSION_UNSUPPORTED ||
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 5aad73b..606afc3 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -285,6 +285,9 @@
   // Returns whether |transport_version| makes use of IETF QUIC
   // frames or not.
   bool HasIetfQuicFrames() const;
+
+  // Returns true if this parsed version supports handshake done.
+  bool HasHandshakeDone() const;
 };
 
 QUIC_EXPORT_PRIVATE ParsedQuicVersion UnsupportedQuicVersion();
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index da549f3..45d1849 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -240,6 +240,9 @@
 }
 
 HandshakeState TlsClientHandshaker::GetHandshakeState() const {
+  if (handshake_confirmed_) {
+    return HANDSHAKE_CONFIRMED;
+  }
   if (one_rtt_keys_available_) {
     return HANDSHAKE_COMPLETE;
   }
@@ -254,6 +257,20 @@
   return TlsHandshaker::BufferSizeLimitForLevel(level);
 }
 
+void TlsClientHandshaker::OnHandshakeDoneReceived() {
+  if (!one_rtt_keys_available_) {
+    CloseConnection(QUIC_HANDSHAKE_FAILED,
+                    "Unexpected handshake done received");
+    return;
+  }
+  if (handshake_confirmed_) {
+    return;
+  }
+  handshake_confirmed_ = true;
+  delegate()->DiscardOldEncryptionKey(ENCRYPTION_HANDSHAKE);
+  delegate()->DiscardOldDecryptionKey(ENCRYPTION_HANDSHAKE);
+}
+
 void TlsClientHandshaker::AdvanceHandshake() {
   if (state_ == STATE_CONNECTION_CLOSED) {
     QUIC_LOG(INFO)
@@ -359,9 +376,6 @@
       SSL_get_peer_signature_algorithm(ssl());
 
   delegate()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
-  // TODO(fayang): Replace this with DiscardOldKeys(ENCRYPTION_HANDSHAKE) when
-  // handshake key discarding settles down.
-  delegate()->NeuterHandshakeData();
 }
 
 enum ssl_verify_result_t TlsClientHandshaker::VerifyCert(uint8_t* out_alert) {
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h
index c8f58bd..4c7028a 100644
--- a/quic/core/tls_client_handshaker.h
+++ b/quic/core/tls_client_handshaker.h
@@ -51,6 +51,7 @@
   CryptoMessageParser* crypto_message_parser() override;
   HandshakeState GetHandshakeState() const override;
   size_t BufferSizeLimitForLevel(EncryptionLevel level) const override;
+  void OnHandshakeDoneReceived() override;
 
   // Override to drop initial keys if trying to write ENCRYPTION_HANDSHAKE data.
   void WriteMessage(EncryptionLevel level,
@@ -138,6 +139,7 @@
 
   bool encryption_established_ = false;
   bool one_rtt_keys_available_ = false;
+  bool handshake_confirmed_ = false;
   QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters>
       crypto_negotiated_params_;
 
diff --git a/quic/core/tls_handshaker_test.cc b/quic/core/tls_handshaker_test.cc
index 6812549..12b3299 100644
--- a/quic/core/tls_handshaker_test.cc
+++ b/quic/core/tls_handshaker_test.cc
@@ -254,6 +254,7 @@
   TlsHandshaker* handshaker() const override { return handshaker_.get(); }
   TlsClientHandshaker* client_handshaker() const { return handshaker_.get(); }
   const MockProofHandler& proof_handler() { return proof_handler_; }
+  void OnHandshakeDoneReceived() override {}
 
   bool CryptoConnect() { return handshaker_->CryptoConnect(); }
 
@@ -306,6 +307,7 @@
   void OnPacketDecrypted(EncryptionLevel level) override {
     handshaker_->OnPacketDecrypted(level);
   }
+  void OnHandshakeDoneReceived() override { DCHECK(false); }
 
   TlsHandshaker* handshaker() const override { return handshaker_.get(); }
 
@@ -369,8 +371,8 @@
     EXPECT_TRUE(client_stream_->encryption_established());
     EXPECT_TRUE(server_stream_->one_rtt_keys_available());
     EXPECT_TRUE(server_stream_->encryption_established());
-    EXPECT_TRUE(client_conn_->IsHandshakeComplete());
-    EXPECT_TRUE(server_conn_->IsHandshakeComplete());
+    EXPECT_EQ(HANDSHAKE_COMPLETE, client_stream_->GetHandshakeState());
+    EXPECT_EQ(HANDSHAKE_CONFIRMED, server_stream_->GetHandshakeState());
 
     const auto& client_crypto_params =
         client_stream_->crypto_negotiated_params();
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index b22d964..1c95d2b 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -120,6 +120,10 @@
   }
 }
 
+void TlsServerHandshaker::OnHandshakeDoneReceived() {
+  DCHECK(false);
+}
+
 bool TlsServerHandshaker::ShouldSendExpectCTHeader() const {
   return false;
 }
@@ -143,7 +147,7 @@
 
 HandshakeState TlsServerHandshaker::GetHandshakeState() const {
   if (one_rtt_keys_available_) {
-    return HANDSHAKE_COMPLETE;
+    return HANDSHAKE_CONFIRMED;
   }
   if (state_ >= STATE_ENCRYPTION_HANDSHAKE_DATA_PROCESSED) {
     return HANDSHAKE_PROCESSED;
@@ -284,9 +288,8 @@
   crypto_negotiated_params_->key_exchange_group = SSL_get_curve_id(ssl());
 
   delegate()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
-  // TODO(fayang): Replace this with DiscardOldKeys(ENCRYPTION_HANDSHAKE) when
-  // handshake key discarding settles down.
-  delegate()->NeuterHandshakeData();
+  delegate()->DiscardOldEncryptionKey(ENCRYPTION_HANDSHAKE);
+  delegate()->DiscardOldDecryptionKey(ENCRYPTION_HANDSHAKE);
 }
 
 ssl_private_key_result_t TlsServerHandshaker::PrivateKeySign(
diff --git a/quic/core/tls_server_handshaker.h b/quic/core/tls_server_handshaker.h
index f2341cf..8215bb7 100644
--- a/quic/core/tls_server_handshaker.h
+++ b/quic/core/tls_server_handshaker.h
@@ -47,6 +47,7 @@
   void SetPreviousCachedNetworkParams(
       CachedNetworkParameters cached_network_params) override;
   void OnPacketDecrypted(EncryptionLevel level) override;
+  void OnHandshakeDoneReceived() override;
   bool ShouldSendExpectCTHeader() const override;
 
   // From QuicCryptoServerStreamBase and TlsHandshaker