Adds "before frame send" and "after frame send" callback support to the Http2Adapter APIs.

PiperOrigin-RevId: 381070368
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
index 042d32b..e953d46 100644
--- a/http2/adapter/callback_visitor.cc
+++ b/http2/adapter/callback_visitor.cc
@@ -91,11 +91,7 @@
 }
 
 void CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
-  auto it = stream_map_.find(stream_id);
-  if (it == stream_map_.end()) {
-    auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()});
-    it = p.first;
-  }
+  auto it = GetStreamInfo(stream_id);
   if (it->second->received_headers) {
     // At least one headers frame has already been received.
     current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS;
@@ -200,7 +196,79 @@
 void CallbackVisitor::OnWindowUpdate(Http2StreamId stream_id,
                                      int window_increment) {
   current_frame_.window_update.window_size_increment = window_increment;
-  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+  if (callbacks_->on_frame_recv_callback) {
+    callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+  }
+}
+
+void CallbackVisitor::PopulateFrame(nghttp2_frame& frame, uint8_t frame_type,
+                                    Http2StreamId stream_id, size_t length,
+                                    uint8_t flags, uint32_t error_code,
+                                    bool sent_headers) {
+  frame.hd.type = frame_type;
+  frame.hd.stream_id = stream_id;
+  frame.hd.length = length;
+  frame.hd.flags = flags;
+  const FrameType frame_type_enum = static_cast<FrameType>(frame_type);
+  if (frame_type_enum == FrameType::HEADERS) {
+    if (sent_headers) {
+      frame.headers.cat = NGHTTP2_HCAT_HEADERS;
+    } else {
+      switch (perspective_) {
+        case Perspective::kClient:
+          QUICHE_LOG(INFO) << "First headers sent by the client for stream "
+                           << stream_id << "; these are request headers";
+          frame.headers.cat = NGHTTP2_HCAT_REQUEST;
+          break;
+        case Perspective::kServer:
+          QUICHE_LOG(INFO) << "First headers sent by the server for stream "
+                           << stream_id << "; these are response headers";
+          frame.headers.cat = NGHTTP2_HCAT_RESPONSE;
+          break;
+      }
+    }
+  } else if (frame_type_enum == FrameType::RST_STREAM) {
+    frame.rst_stream.error_code = error_code;
+  } else if (frame_type_enum == FrameType::GOAWAY) {
+    frame.goaway.error_code = error_code;
+  }
+}
+
+int CallbackVisitor::OnBeforeFrameSent(uint8_t frame_type,
+                                       Http2StreamId stream_id, size_t length,
+                                       uint8_t flags) {
+  if (callbacks_->before_frame_send_callback) {
+    QUICHE_LOG(INFO) << "OnBeforeFrameSent(type=" << int(frame_type)
+                     << ", stream_id=" << stream_id << ", length=" << length
+                     << ", flags=" << int(flags) << ")";
+    nghttp2_frame frame;
+    auto it = GetStreamInfo(stream_id);
+    // The implementation of the before_frame_send_callback doesn't look at the
+    // error code, so for now it's populated with 0.
+    PopulateFrame(frame, frame_type, stream_id, length, flags, /*error_code=*/0,
+                  it->second->before_sent_headers);
+    it->second->before_sent_headers = true;
+    return callbacks_->before_frame_send_callback(nullptr, &frame, user_data_);
+  }
+  return 0;
+}
+
+int CallbackVisitor::OnFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+                                 size_t length, uint8_t flags,
+                                 uint32_t error_code) {
+  if (callbacks_->on_frame_send_callback) {
+    QUICHE_LOG(INFO) << "OnFrameSent(type=" << int(frame_type)
+                     << ", stream_id=" << stream_id << ", length=" << length
+                     << ", flags=" << int(flags)
+                     << ", error_code=" << error_code << ")";
+    nghttp2_frame frame;
+    auto it = GetStreamInfo(stream_id);
+    PopulateFrame(frame, frame_type, stream_id, length, flags, error_code,
+                  it->second->sent_headers);
+    it->second->sent_headers = true;
+    return callbacks_->on_frame_send_callback(nullptr, &frame, user_data_);
+  }
+  return 0;
 }
 
 void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
@@ -232,5 +300,15 @@
   QUICHE_LOG(FATAL) << "Not implemented";
 }
 
+CallbackVisitor::StreamInfoMap::iterator CallbackVisitor::GetStreamInfo(
+    Http2StreamId stream_id) {
+  auto it = stream_map_.find(stream_id);
+  if (it == stream_map_.end()) {
+    auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()});
+    it = p.first;
+  }
+  return it;
+}
+
 }  // namespace adapter
 }  // namespace http2
diff --git a/http2/adapter/callback_visitor.h b/http2/adapter/callback_visitor.h
index 5070797..07b3608 100644
--- a/http2/adapter/callback_visitor.h
+++ b/http2/adapter/callback_visitor.h
@@ -53,6 +53,10 @@
                 Http2ErrorCode error_code,
                 absl::string_view opaque_data) override;
   void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+  int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+                        size_t length, uint8_t flags) override;
+  int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length,
+                  uint8_t flags, uint32_t error_code) override;
   void OnReadyToSendDataForStream(Http2StreamId stream_id,
                                   char* destination_buffer,
                                   size_t length,
@@ -69,6 +73,21 @@
   void OnMetadataEndForStream(Http2StreamId stream_id) override;
 
  private:
+  struct StreamInfo {
+    bool before_sent_headers = false;
+    bool sent_headers = false;
+    bool received_headers = false;
+  };
+
+  using StreamInfoMap =
+      absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>>;
+
+  void PopulateFrame(nghttp2_frame& frame, uint8_t frame_type,
+                     Http2StreamId stream_id, size_t length, uint8_t flags,
+                     uint32_t error_code, bool sent_headers);
+  // Creates the StreamInfoMap entry if it doesn't exist.
+  StreamInfoMap::iterator GetStreamInfo(Http2StreamId stream_id);
+
   Perspective perspective_;
   nghttp2_session_callbacks_unique_ptr callbacks_;
   void* user_data_;
@@ -77,10 +96,7 @@
   std::vector<nghttp2_settings_entry> settings_;
   size_t remaining_data_ = 0;
 
-  struct StreamInfo {
-    bool received_headers = false;
-  };
-  absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>> stream_map_;
+  StreamInfoMap stream_map_;
 };
 
 }  // namespace adapter
diff --git a/http2/adapter/http2_protocol.h b/http2/adapter/http2_protocol.h
index faefae7..7cefa29 100644
--- a/http2/adapter/http2_protocol.h
+++ b/http2/adapter/http2_protocol.h
@@ -62,6 +62,19 @@
 ABSL_CONST_INIT extern const char kHttp2PathPseudoHeader[];
 ABSL_CONST_INIT extern const char kHttp2StatusPseudoHeader[];
 
+enum class FrameType : uint8_t {
+  DATA = 0x0,
+  HEADERS,
+  PRIORITY,
+  RST_STREAM,
+  SETTINGS,
+  PUSH_PROMISE,
+  PING,
+  GOAWAY,
+  WINDOW_UPDATE,
+  CONTINUATION,
+};
+
 // HTTP/2 error codes as specified in RFC 7540 Section 7.
 enum class Http2ErrorCode {
   NO_ERROR = 0x0,
diff --git a/http2/adapter/http2_visitor_interface.h b/http2/adapter/http2_visitor_interface.h
index 8e621e4..f174329 100644
--- a/http2/adapter/http2_visitor_interface.h
+++ b/http2/adapter/http2_visitor_interface.h
@@ -140,6 +140,18 @@
   virtual void OnWindowUpdate(Http2StreamId stream_id,
                               int window_increment) = 0;
 
+  // Called immediately before a frame of the given type is sent. Should return
+  // 0 on success.
+  virtual int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+                                size_t length, uint8_t flags) = 0;
+
+  // Called immediately after a frame of the given type is sent. Should return 0
+  // on success. |error_code| is only populated for RST_STREAM and GOAWAY frame
+  // types.
+  virtual int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+                          size_t length, uint8_t flags,
+                          uint32_t error_code) = 0;
+
   // Called when the connection is ready to send data for a stream. The
   // implementation should write at most |length| bytes of the data payload to
   // the |destination_buffer| and set |end_stream| to true IFF there will be no
diff --git a/http2/adapter/mock_http2_visitor.h b/http2/adapter/mock_http2_visitor.h
index 09a9077..bacdd4f 100644
--- a/http2/adapter/mock_http2_visitor.h
+++ b/http2/adapter/mock_http2_visitor.h
@@ -93,6 +93,15 @@
               (Http2StreamId stream_id, int window_increment),
               (override));
 
+  MOCK_METHOD(int, OnBeforeFrameSent,
+              (uint8_t frame_type, Http2StreamId stream_id, size_t length,
+               uint8_t flags),
+              (override));
+  MOCK_METHOD(int, OnFrameSent,
+              (uint8_t frame_type, Http2StreamId stream_id, size_t length,
+               uint8_t flags, uint32_t error_code),
+              (override));
+
   MOCK_METHOD(void,
               OnReadyToSendDataForStream,
               (Http2StreamId stream_id,
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index d2875eb..11f3921 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -86,8 +86,14 @@
   EXPECT_EQ(initial_frames.size(), initial_result);
 
   EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000);
-  // Some bytes should have been serialized.
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+
   result = adapter->Send();
+  // Some bytes should have been serialized.
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
                                             spdy::SpdyFrameType::PING}));
@@ -131,6 +137,13 @@
   adapter->SetStreamUserData(stream_id2, const_cast<char*>(kSentinel2));
   adapter->SetStreamUserData(stream_id3, nullptr);
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id2, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id2, _, 0x5, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id3, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id3, _, 0x5, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -263,6 +276,9 @@
   ASSERT_GT(stream_id1, 0);
   QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
   int result = adapter->Send();
   EXPECT_EQ(0, result);
   absl::string_view data = visitor.data();
@@ -309,6 +325,9 @@
   const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
   EXPECT_EQ(stream_frames.size(), stream_result);
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   EXPECT_TRUE(adapter->session().want_write());
   result = adapter->Send();
   EXPECT_EQ(0, result);
@@ -333,6 +352,9 @@
   ASSERT_GT(stream_id1, 0);
   QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
   int result = adapter->Send();
   EXPECT_EQ(0, result);
   absl::string_view data = visitor.data();
@@ -374,11 +396,16 @@
 
   // Bad status trailer will cause a PROTOCOL_ERROR. The header is never
   // delivered in an OnHeaderForStream callback.
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
 
   const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
   EXPECT_EQ(stream_frames.size(), stream_result);
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+
   EXPECT_TRUE(adapter->session().want_write());
   result = adapter->Send();
   EXPECT_EQ(0, result);
@@ -409,6 +436,10 @@
   EXPECT_EQ(initial_frames.size(), initial_result);
 
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
@@ -427,6 +458,11 @@
                              std::move(body1), const_cast<char*>(kSentinel));
   EXPECT_GT(stream_id, 0);
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
 
@@ -464,6 +500,9 @@
   EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id));
   adapter->SetStreamUserData(stream_id, const_cast<char*>(kSentinel2));
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
@@ -501,6 +540,10 @@
   EXPECT_EQ(initial_frames.size(), initial_result);
 
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
@@ -526,6 +569,11 @@
                              std::move(frame_source), nullptr);
   EXPECT_GT(stream_id, 0);
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -561,6 +609,9 @@
   EXPECT_GT(stream_id, 0);
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
   int result = adapter->Send();
   EXPECT_EQ(0, result);
   // Client preface does not appear to include the mandatory SETTINGS frame.
@@ -577,6 +628,8 @@
   EXPECT_TRUE(adapter->ResumeStream(stream_id));
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = adapter->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::DATA}));
@@ -619,6 +672,10 @@
   EXPECT_THAT(visitor.data(), testing::IsEmpty());
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   visitor.set_is_write_blocked(false);
   result = adapter->Send();
   EXPECT_EQ(0, result);
@@ -738,8 +795,16 @@
   EXPECT_EQ(adapter->GetSendWindowSize(), kInitialFlowControlWindowSize + 1000);
 
   EXPECT_TRUE(adapter->session().want_write());
-  // Some bytes should have been serialized.
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x1, 0));
+
   int send_result = adapter->Send();
+  // Some bytes should have been serialized.
   EXPECT_EQ(0, send_result);
   // SETTINGS ack, two PING acks.
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
@@ -789,6 +854,10 @@
 
   // Server will want to send a SETTINGS ack.
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
@@ -813,6 +882,10 @@
   adapter->SetStreamUserData(1, nullptr);
   EXPECT_EQ(nullptr, adapter->GetStreamUserData(1));
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
 
@@ -867,6 +940,12 @@
   adapter->SubmitShutdownNotice();
 
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
@@ -908,6 +987,10 @@
 
   // Server will want to send a SETTINGS ack.
   EXPECT_TRUE(adapter->session().want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
@@ -925,7 +1008,11 @@
       std::move(body1));
   EXPECT_EQ(submit_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -941,6 +1028,10 @@
   ASSERT_EQ(trailer_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
   send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
@@ -990,7 +1081,13 @@
       std::move(body1));
   EXPECT_EQ(submit_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
@@ -1006,6 +1103,10 @@
   ASSERT_EQ(trailer_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
   send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
diff --git a/http2/adapter/nghttp2_callbacks.cc b/http2/adapter/nghttp2_callbacks.cc
index d0895c1..d2c7e6d 100644
--- a/http2/adapter/nghttp2_callbacks.cc
+++ b/http2/adapter/nghttp2_callbacks.cc
@@ -166,6 +166,28 @@
   return success ? 0 : NGHTTP2_ERR_HTTP_HEADER;
 }
 
+int OnBeforeFrameSent(nghttp2_session* /* session */,
+                      const nghttp2_frame* frame, void* user_data) {
+  QUICHE_CHECK_NE(user_data, nullptr);
+  auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+  return visitor->OnBeforeFrameSent(frame->hd.type, frame->hd.stream_id,
+                                    frame->hd.length, frame->hd.flags);
+}
+
+int OnFrameSent(nghttp2_session* /* session */, const nghttp2_frame* frame,
+                void* user_data) {
+  QUICHE_CHECK_NE(user_data, nullptr);
+  auto* visitor = static_cast<Http2VisitorInterface*>(user_data);
+  uint32_t error_code = 0;
+  if (frame->hd.type == NGHTTP2_RST_STREAM) {
+    error_code = frame->rst_stream.error_code;
+  } else if (frame->hd.type == NGHTTP2_GOAWAY) {
+    error_code = frame->goaway.error_code;
+  }
+  return visitor->OnFrameSent(frame->hd.type, frame->hd.stream_id,
+                              frame->hd.length, frame->hd.flags, error_code);
+}
+
 int OnDataChunk(nghttp2_session* /* session */,
                 uint8_t flags,
                 Http2StreamId stream_id,
@@ -205,6 +227,9 @@
                                                             &OnDataChunk);
   nghttp2_session_callbacks_set_on_stream_close_callback(callbacks,
                                                          &OnStreamClosed);
+  nghttp2_session_callbacks_set_before_frame_send_callback(callbacks,
+                                                           &OnBeforeFrameSent);
+  nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, &OnFrameSent);
   nghttp2_session_callbacks_set_send_data_callback(
       callbacks, &DataFrameSourceSendCallback);
   return MakeCallbacksPtr(callbacks);
diff --git a/http2/adapter/nghttp2_callbacks.h b/http2/adapter/nghttp2_callbacks.h
index 68fa083..8f026fa 100644
--- a/http2/adapter/nghttp2_callbacks.h
+++ b/http2/adapter/nghttp2_callbacks.h
@@ -38,7 +38,15 @@
              nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags,
              void* user_data);
 
-// Callback once a chunk of data (from a DATA frame payload) has been received.
+// Invoked immediately before sending a frame.
+int OnBeforeFrameSent(nghttp2_session* session, const nghttp2_frame* frame,
+                      void* user_data);
+
+// Invoked immediately after a frame is sent.
+int OnFrameSent(nghttp2_session* session, const nghttp2_frame* frame,
+                void* user_data);
+
+// Invoked when a chunk of data (from a DATA frame payload) has been received.
 int OnDataChunk(nghttp2_session* session, uint8_t flags,
                 Http2StreamId stream_id, const uint8_t* data, size_t len,
                 void* user_data);
diff --git a/http2/adapter/nghttp2_session_test.cc b/http2/adapter/nghttp2_session_test.cc
index d4e5ea1..487843b 100644
--- a/http2/adapter/nghttp2_session_test.cc
+++ b/http2/adapter/nghttp2_session_test.cc
@@ -82,6 +82,12 @@
 
   EXPECT_EQ(session.GetRemoteWindowSize(),
             kInitialFlowControlWindowSize + 1000);
+
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
+
   ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
   // Some bytes should have been serialized.
   absl::string_view serialized = visitor_.data();
@@ -128,6 +134,13 @@
   ASSERT_GT(stream_id3, 0);
   QUICHE_LOG(INFO) << "Created stream: " << stream_id3;
 
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 3, _, 0x5));
+  EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 3, _, 0x5, 0));
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(HEADERS, 5, _, 0x5));
+  EXPECT_CALL(visitor_, OnFrameSent(HEADERS, 5, _, 0x5, 0));
+
   ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
   serialized = visitor_.data();
   EXPECT_THAT(serialized, EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -268,6 +281,13 @@
   EXPECT_EQ(session.GetRemoteWindowSize(),
             kInitialFlowControlWindowSize + 1000);
 
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor_, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
+  EXPECT_CALL(visitor_, OnBeforeFrameSent(PING, 0, 8, 0x1));
+  EXPECT_CALL(visitor_, OnFrameSent(PING, 0, 8, 0x1, 0));
+
   EXPECT_TRUE(session.want_write());
   ASSERT_EQ(0, nghttp2_session_send(session.raw_ptr()));
   // Some bytes should have been serialized.
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 543fab4..3ba26e4 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -72,6 +72,11 @@
   ASSERT_GT(stream_id1, 0);
   QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
   int result = adapter->Send();
   EXPECT_EQ(0, result);
   absl::string_view data = visitor.data();
@@ -119,6 +124,9 @@
   const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
   EXPECT_EQ(stream_frames.size(), stream_result);
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   EXPECT_TRUE(adapter->session().want_write());
   result = adapter->Send();
   EXPECT_EQ(0, result);
@@ -145,6 +153,9 @@
   ASSERT_GT(stream_id1, 0);
   QUICHE_LOG(INFO) << "Created stream: " << stream_id1;
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id1, _, 0x5, 0));
+
   int result = adapter->Send();
   EXPECT_EQ(0, result);
   absl::string_view data = visitor.data();
@@ -187,11 +198,16 @@
 
   // Bad status trailer will cause a PROTOCOL_ERROR. The header is never
   // delivered in an OnHeaderForStream callback.
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
 
   const ssize_t stream_result = adapter->ProcessBytes(stream_frames);
   EXPECT_EQ(stream_frames.size(), stream_result);
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, stream_id1, 4, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(RST_STREAM, stream_id1, 4, 0x0, 1));
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+
   EXPECT_TRUE(adapter->session().want_write());
   result = adapter->Send();
   EXPECT_EQ(0, result);
@@ -228,6 +244,19 @@
   adapter_->SubmitWindowUpdate(3, 127);
   EXPECT_TRUE(adapter_->session().want_write());
 
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PRIORITY, 3, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(PRIORITY, 3, _, 0x0, 0));
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(RST_STREAM, 3, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(RST_STREAM, 3, _, 0x0, 0x8));
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0));
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(WINDOW_UPDATE, 3, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(WINDOW_UPDATE, 3, _, 0x0, 0));
+
   int result = adapter_->Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(
@@ -248,12 +277,18 @@
   EXPECT_TRUE(adapter_->session().want_write());
 
   http2_visitor_.set_send_limit(20);
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
   int result = adapter_->Send();
   EXPECT_EQ(0, result);
   EXPECT_TRUE(adapter_->session().want_write());
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
   result = adapter_->Send();
   EXPECT_EQ(0, result);
   EXPECT_TRUE(adapter_->session().want_write());
+  EXPECT_CALL(http2_visitor_, OnBeforeFrameSent(PING, 0, _, 0x0));
+  EXPECT_CALL(http2_visitor_, OnFrameSent(PING, 0, _, 0x0, 0));
   result = adapter_->Send();
   EXPECT_EQ(0, result);
   EXPECT_FALSE(adapter_->session().want_write());
@@ -307,7 +342,15 @@
       std::move(body1));
   EXPECT_EQ(submit_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   int send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -319,11 +362,15 @@
   EXPECT_FALSE(adapter->session().want_write());
 
   // The body source has been exhausted by the call to Send() above.
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
   int trailer_result =
       adapter->SubmitTrailer(1, ToHeaders({{":final-status", "a-ok"}}));
   ASSERT_EQ(trailer_result, 0);
   EXPECT_TRUE(adapter->session().want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
   send_result = adapter->Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index b9962a3..5532a37 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -6,6 +6,99 @@
 namespace http2 {
 namespace adapter {
 
+namespace {
+
+// TODO(birenroy): Consider incorporating spdy::FlagsSerializionVisitor here.
+class FrameAttributeCollector : public spdy::SpdyFrameVisitor {
+ public:
+  FrameAttributeCollector() = default;
+  void VisitData(const spdy::SpdyDataIR& data) override {
+    frame_type_ = static_cast<uint8_t>(data.frame_type());
+    stream_id_ = data.stream_id();
+    length_ =
+        data.data_len() + (data.padded() ? 1 : 0) + data.padding_payload_len();
+    flags_ = (data.fin() ? 0x1 : 0) | (data.padded() ? 0x8 : 0);
+  }
+  void VisitHeaders(const spdy::SpdyHeadersIR& headers) override {
+    frame_type_ = static_cast<uint8_t>(headers.frame_type());
+    stream_id_ = headers.stream_id();
+    length_ = headers.size() - spdy::kFrameHeaderSize;
+    flags_ = 0x4 | (headers.fin() ? 0x1 : 0) | (headers.padded() ? 0x8 : 0) |
+             (headers.has_priority() ? 0x20 : 0);
+  }
+  void VisitPriority(const spdy::SpdyPriorityIR& priority) override {
+    frame_type_ = static_cast<uint8_t>(priority.frame_type());
+    frame_type_ = 2;
+    length_ = 5;
+    stream_id_ = priority.stream_id();
+  }
+  void VisitRstStream(const spdy::SpdyRstStreamIR& rst_stream) override {
+    frame_type_ = static_cast<uint8_t>(rst_stream.frame_type());
+    frame_type_ = 3;
+    length_ = 4;
+    stream_id_ = rst_stream.stream_id();
+    error_code_ = rst_stream.error_code();
+  }
+  void VisitSettings(const spdy::SpdySettingsIR& settings) override {
+    frame_type_ = static_cast<uint8_t>(settings.frame_type());
+    frame_type_ = 4;
+    length_ = 6 * settings.values().size();
+    flags_ = (settings.is_ack() ? 0x1 : 0);
+  }
+  void VisitPushPromise(const spdy::SpdyPushPromiseIR& push_promise) override {
+    frame_type_ = static_cast<uint8_t>(push_promise.frame_type());
+    frame_type_ = 5;
+    length_ = push_promise.size() - spdy::kFrameHeaderSize;
+    stream_id_ = push_promise.stream_id();
+    flags_ = (push_promise.padded() ? 0x8 : 0);
+  }
+  void VisitPing(const spdy::SpdyPingIR& ping) override {
+    frame_type_ = static_cast<uint8_t>(ping.frame_type());
+    frame_type_ = 6;
+    length_ = 8;
+    flags_ = (ping.is_ack() ? 0x1 : 0);
+  }
+  void VisitGoAway(const spdy::SpdyGoAwayIR& goaway) override {
+    frame_type_ = static_cast<uint8_t>(goaway.frame_type());
+    frame_type_ = 7;
+    length_ = goaway.size() - spdy::kFrameHeaderSize;
+    error_code_ = goaway.error_code();
+  }
+  void VisitWindowUpdate(
+      const spdy::SpdyWindowUpdateIR& window_update) override {
+    frame_type_ = static_cast<uint8_t>(window_update.frame_type());
+    frame_type_ = 8;
+    length_ = 4;
+    stream_id_ = window_update.stream_id();
+  }
+  void VisitContinuation(
+      const spdy::SpdyContinuationIR& continuation) override {
+    frame_type_ = static_cast<uint8_t>(continuation.frame_type());
+    stream_id_ = continuation.stream_id();
+    flags_ = continuation.end_headers() ? 0x4 : 0;
+    length_ = continuation.size() - spdy::kFrameHeaderSize;
+  }
+  void VisitAltSvc(const spdy::SpdyAltSvcIR& altsvc) override {}
+  void VisitPriorityUpdate(
+      const spdy::SpdyPriorityUpdateIR& priority_update) override {}
+  void VisitAcceptCh(const spdy::SpdyAcceptChIR& accept_ch) override {}
+
+  uint32_t stream_id() { return stream_id_; }
+  uint32_t length() { return length_; }
+  uint32_t error_code() { return error_code_; }
+  uint8_t frame_type() { return frame_type_; }
+  uint8_t flags() { return flags_; }
+
+ private:
+  uint32_t stream_id_ = 0;
+  uint32_t length_ = 0;
+  uint32_t error_code_ = 0;
+  uint8_t frame_type_ = 0;
+  uint8_t flags_ = 0;
+};
+
+}  // namespace
+
 void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() {
   visitor_.OnBeginHeadersForStream(stream_id_);
 }
@@ -194,7 +287,12 @@
 bool OgHttp2Session::SendQueuedFrames() {
   // Serialize and send frames in the queue.
   while (!frames_.empty()) {
-    spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frames_.front());
+    const auto& frame_ptr = frames_.front();
+    FrameAttributeCollector c;
+    frame_ptr->Visit(&c);
+    visitor_.OnBeforeFrameSent(c.frame_type(), c.stream_id(), c.length(),
+                               c.flags());
+    spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr);
     const ssize_t result = visitor_.OnReadyToSend(absl::string_view(frame));
     if (result < 0) {
       visitor_.OnConnectionError();
@@ -203,6 +301,8 @@
       // Write blocked.
       return false;
     } else {
+      visitor_.OnFrameSent(c.frame_type(), c.stream_id(), c.length(), c.flags(),
+                           c.error_code());
       frames_.pop_front();
       if (result < frame.size()) {
         // The frame was partially written, so the rest must be buffered.
@@ -262,6 +362,7 @@
       connection_can_write = false;
       break;
     }
+    visitor_.OnFrameSent(/* DATA */ 0, stream_id, length, fin ? 0x1 : 0x0, 0);
     connection_send_window_ -= length;
     state.send_window -= length;
     available_window =
@@ -316,12 +417,13 @@
     QUICHE_LOG(DFATAL) << "Stream " << stream_id << " already exists!";
     return -501;  // NGHTTP2_ERR_INVALID_ARGUMENT
   }
-  iter->second.outbound_body = std::move(data_source);
-  iter->second.user_data = user_data;
   if (data_source == nullptr) {
     frame->set_fin(true);
     iter->second.half_closed_local = true;
+  } else {
+    iter->second.outbound_body = std::move(data_source);
   }
+  iter->second.user_data = user_data;
   // Add the stream to the write scheduler.
   const WriteScheduler::StreamPrecedenceType precedence(3);
   write_scheduler_.RegisterStream(stream_id, precedence);
diff --git a/http2/adapter/oghttp2_session_test.cc b/http2/adapter/oghttp2_session_test.cc
index 97ec7df..137d204 100644
--- a/http2/adapter/oghttp2_session_test.cc
+++ b/http2/adapter/oghttp2_session_test.cc
@@ -139,6 +139,10 @@
   OgHttp2Session session(
       visitor, OgHttp2Session::Options{.perspective = Perspective::kClient});
   EXPECT_FALSE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   absl::string_view serialized = visitor.data();
@@ -157,6 +161,12 @@
   EXPECT_FALSE(session.want_write());
   session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, 8, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(PING, 0, 8, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   absl::string_view serialized = visitor.data();
@@ -176,6 +186,10 @@
   EXPECT_FALSE(session.want_write());
   session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   absl::string_view serialized = visitor.data();
@@ -192,6 +206,9 @@
 
   EXPECT_FALSE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
   // Even though the user has not queued any frames for the session, it should
   // still send the connection preface.
   int result = session.Send();
@@ -218,6 +235,10 @@
 
   // Session will want to write a SETTINGS ack.
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
@@ -237,6 +258,11 @@
   EXPECT_GT(stream_id, 0);
   EXPECT_TRUE(session.want_write());
   EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
@@ -267,6 +293,9 @@
   session.SetStreamUserData(stream_id, const_cast<char*>(kSentinel2));
   EXPECT_EQ(kSentinel2, session.GetStreamUserData(stream_id));
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
   result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
@@ -299,6 +328,12 @@
   EXPECT_GT(stream_id, 0);
   EXPECT_TRUE(session.want_write());
   EXPECT_EQ(kSentinel1, session.GetStreamUserData(stream_id));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   absl::string_view serialized = visitor.data();
@@ -314,6 +349,9 @@
   body_ref->set_is_data_available(true);
   EXPECT_TRUE(session.ResumeStream(stream_id));
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::DATA}));
@@ -351,6 +389,13 @@
   EXPECT_THAT(visitor.data(), testing::IsEmpty());
   EXPECT_TRUE(session.want_write());
   visitor.set_is_write_blocked(false);
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, stream_id, _, 0x1, 0));
+
   result = session.Send();
   EXPECT_EQ(0, result);
 
@@ -375,6 +420,9 @@
   session.StartGracefulShutdown();
   EXPECT_FALSE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
 
@@ -494,6 +542,12 @@
   EXPECT_EQ(3, session.GetHighestReceivedStreamId());
 
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   // Some bytes should have been serialized.
   int send_result = session.Send();
   EXPECT_EQ(0, send_result);
@@ -512,6 +566,12 @@
   EXPECT_FALSE(session.want_write());
   session.EnqueueFrame(absl::make_unique<spdy::SpdyPingIR>(42));
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(PING, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(PING, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(),
@@ -527,6 +587,10 @@
   EXPECT_FALSE(session.want_write());
   session.EnqueueFrame(absl::make_unique<spdy::SpdySettingsIR>());
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
@@ -578,6 +642,12 @@
 
   // Server will want to send initial SETTINGS, and a SETTINGS ack.
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   int send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -601,6 +671,10 @@
   session.SetStreamUserData(1, nullptr);
   EXPECT_EQ(nullptr, session.GetStreamUserData(1));
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -626,6 +700,11 @@
   session.StartGracefulShutdown();
   EXPECT_TRUE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(),
@@ -644,6 +723,11 @@
   session.EnqueueFrame(std::move(goaway));
   EXPECT_TRUE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(GOAWAY, 0, _, 0x0, 0));
+
   int result = session.Send();
   EXPECT_EQ(0, result);
   EXPECT_THAT(visitor.data(),
@@ -693,6 +777,12 @@
 
   // Server will want to send initial SETTINGS, and a SETTINGS ack.
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   int send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -711,6 +801,11 @@
       std::move(body1));
   EXPECT_EQ(submit_result, 0);
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
   send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -718,14 +813,18 @@
   visitor.Clear();
   EXPECT_FALSE(session.want_write());
 
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
   // The body source has been exhausted by the call to Send() above.
+  // TODO(birenroy): Fix this strange ordering.
+  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
   int trailer_result = session.SubmitTrailer(
       1, ToHeaders({{"final-status", "a-ok"},
                     {"x-comment", "trailers sure are cool"}}));
   ASSERT_EQ(trailer_result, 0);
   EXPECT_TRUE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
   send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::HEADERS}));
@@ -770,6 +869,12 @@
 
   // Server will want to send initial SETTINGS, and a SETTINGS ack.
   EXPECT_TRUE(session.want_write());
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0));
+
   int send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
@@ -796,7 +901,16 @@
   ASSERT_EQ(trailer_result, 0);
   EXPECT_TRUE(session.want_write());
 
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0));
+  EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0));
+
+  // TODO(birenroy): Fix this strange ordering.
   EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x5, 0));
+
   send_result = session.Send();
   EXPECT_EQ(0, send_result);
   EXPECT_THAT(visitor.data(),
diff --git a/http2/adapter/recording_http2_visitor.cc b/http2/adapter/recording_http2_visitor.cc
index bc07fe4..15dce0c 100644
--- a/http2/adapter/recording_http2_visitor.cc
+++ b/http2/adapter/recording_http2_visitor.cc
@@ -118,6 +118,22 @@
       absl::StrFormat("OnWindowUpdate %d %d", stream_id, window_increment));
 }
 
+int RecordingHttp2Visitor::OnBeforeFrameSent(uint8_t frame_type,
+                                             Http2StreamId stream_id,
+                                             size_t length, uint8_t flags) {
+  events_.push_back(absl::StrFormat("OnBeforeFrameSent %d %d %d %d", frame_type,
+                                    stream_id, length, flags));
+  return 0;
+}
+
+int RecordingHttp2Visitor::OnFrameSent(uint8_t frame_type,
+                                       Http2StreamId stream_id, size_t length,
+                                       uint8_t flags, uint32_t error_code) {
+  events_.push_back(absl::StrFormat("OnFrameSent %d %d %d %d %d", frame_type,
+                                    stream_id, length, flags, error_code));
+  return 0;
+}
+
 void RecordingHttp2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
                                                        char* destination_buffer,
                                                        size_t length,
diff --git a/http2/adapter/recording_http2_visitor.h b/http2/adapter/recording_http2_visitor.h
index 74498cc..17c5c52 100644
--- a/http2/adapter/recording_http2_visitor.h
+++ b/http2/adapter/recording_http2_visitor.h
@@ -51,6 +51,10 @@
                 Http2ErrorCode error_code,
                 absl::string_view opaque_data) override;
   void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+  int OnBeforeFrameSent(uint8_t frame_type, Http2StreamId stream_id,
+                        size_t length, uint8_t flags) override;
+  int OnFrameSent(uint8_t frame_type, Http2StreamId stream_id, size_t length,
+                  uint8_t flags, uint32_t error_code) override;
   void OnReadyToSendDataForStream(Http2StreamId stream_id,
                                   char* destination_buffer,
                                   size_t length,