Index: net/spdy/spdy_deframer_visitor.h |
diff --git a/net/spdy/spdy_deframer_visitor.h b/net/spdy/spdy_deframer_visitor.h |
new file mode 100644 |
index 0000000000000000000000000000000000000000..66c3c777f7cbe45da45bcedfbcbb89d2be004dac |
--- /dev/null |
+++ b/net/spdy/spdy_deframer_visitor.h |
@@ -0,0 +1,250 @@ |
+// Copyright 2016 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 NET_SPDY_SPDY_DEFRAMER_VISITOR_H_ |
+#define NET_SPDY_SPDY_DEFRAMER_VISITOR_H_ |
+ |
+// Supports testing by converting callbacks to SpdyFramerVisitorInterface into |
+// callbacks to SpdyDeframerVisitorInterface, whose arguments are generally |
+// SpdyFrameIR instances. This enables a test client or test backend to operate |
+// at a level between the low-level callbacks of SpdyFramerVisitorInterface and |
+// the much higher level of entire messages (i.e. headers, body, trailers). |
+// Where possible the converter (SpdyTestDeframer) tries to preserve information |
+// that might be useful to tests (e.g. the order of headers or the amount of |
+// padding); the design also aims to allow tests to be concise, ideally |
+// supporting gMock style EXPECT_CALL(visitor, OnHeaders(...matchers...)) |
+// without too much boilerplate. |
+// |
+// Only supports HTTP/2 for the moment. |
+// |
+// Example of usage: |
+// |
+// SpdyFramer framer(HTTP2); |
+// |
+// // Need to call SpdyTestDeframer::AtFrameEnd() after processing each |
+// // frame, so tell SpdyFramer to stop after each. |
+// framer.set_process_single_input_frame(true); |
+// |
+// // Need the new OnHeader callbacks. |
+// framer.set_use_new_methods_for_test(true); |
+// |
+// // Create your visitor, a subclass of SpdyDeframerVisitorInterface. |
+// // For example, using DeframerCallbackCollector to collect frames: |
+// std::vector<CollectedFrame> collected_frames; |
+// auto your_visitor = gtl::MakeUnique<DeframerCallbackCollector>( |
+// &collected_frames); |
+// |
+// // Transfer ownership of your visitor to the converter, which ensures that |
+// // your visitor stays alive while the converter needs to call it. |
+// auto the_deframer = SpdyTestDeframer::CreateConverter( |
+// std::move(your_visitor)); |
+// |
+// // Tell the framer to notify SpdyTestDeframer of the decoded frame |
+// // details. |
+// framer.set_visitor(the_deframer.get()); |
+// |
+// // Process frames. |
+// StringPiece input = ... |
+// while (!input.empty() && !framer.HasError()) { |
+// size_t consumed = framer.ProcessInput(input.data(), input.size()); |
+// input.remove_prefix(consumed); |
+// if (framer.state() == SpdyFramer::SPDY_READY_FOR_FRAME) { |
+// the_deframer->AtFrameEnd(); |
+// } |
+// } |
+// |
+// // Make sure that the correct frames were received. For example: |
+// ASSERT_EQ(collected_frames.size(), 3); |
+// |
+// SpdyDataIR expected1(7 /*stream_id*/, "Data Payload"); |
+// expected1.set_padding_len(17); |
+// EXPECT_TRUE(collected_frames[0].VerifyEquals(expected1)); |
+// |
+// // Repeat for the other frames. |
+// |
+// Note that you could also seed the subclass of SpdyDeframerVisitorInterface |
+// with the expected frames, which it would pop-off the list as its expectations |
+// are met. |
+ |
+#include <stdint.h> |
+ |
+#include <memory> |
+#include <string> |
+#include <type_traits> |
+#include <utility> |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "net/spdy/spdy_framer.h" |
+#include "net/spdy/spdy_protocol.h" |
+#include "net/spdy/spdy_protocol_test_utils.h" |
+#include "net/spdy/spdy_test_utils.h" |
+ |
+namespace net { |
+namespace test { |
+ |
+// Non-lossy representation of a SETTINGS frame payload. |
+typedef std::vector<std::pair<SpdySettingsIds, uint32_t>> SettingVector; |
+ |
+// StringPairVector is used to record information lost by SpdyHeaderBlock, in |
+// particular the order of each header entry, though it doesn't expose the |
+// inner details of the HPACK block, such as the type of encoding selected |
+// for each header entry, nor dynamic table size changes. |
+typedef std::pair<std::string, std::string> StringPair; |
+typedef std::vector<StringPair> StringPairVector; |
+ |
+// Forward decl. |
+class SpdyTestDeframer; |
+ |
+// Note that this only roughly captures the frames, as padding bytes are lost, |
+// continuation frames are combined with their leading HEADERS or PUSH_PROMISE, |
+// the details of the HPACK encoding are lost, leaving |
+// only the list of header entries (name and value strings). If really helpful, |
+// we could add a SpdyRawDeframerVisitorInterface that gets the HPACK bytes, |
+// and receives continuation frames. For more info we'd need to improve |
+// SpdyFramerVisitorInterface. |
+class SpdyDeframerVisitorInterface { |
+ public: |
+ virtual ~SpdyDeframerVisitorInterface() {} |
+ |
+ // Wrap a visitor in another SpdyDeframerVisitorInterface that will |
+ // DVLOG each call, and will then forward the calls to the wrapped visitor |
+ // (if provided; nullptr is OK). Takes ownership of the wrapped visitor. |
+ static std::unique_ptr<SpdyDeframerVisitorInterface> LogBeforeVisiting( |
+ std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_visitor); |
+ |
+ virtual void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) {} |
+ virtual void OnData(std::unique_ptr<SpdyDataIR> frame) {} |
+ virtual void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) {} |
+ |
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
+ // significantly modifies the headers, so the actual header entries (name |
+ // and value strings) are provided in a vector. |
+ virtual void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame, |
+ std::unique_ptr<StringPairVector> headers) {} |
+ |
+ virtual void OnPing(std::unique_ptr<SpdyPingIR> frame) {} |
+ virtual void OnPingAck(std::unique_ptr<SpdyPingIR> frame); |
+ virtual void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) {} |
+ |
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which |
+ // significantly modifies the headers, so the actual header entries (name |
+ // and value strings) are provided in a vector. |
+ virtual void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame, |
+ std::unique_ptr<StringPairVector> headers) {} |
+ |
+ virtual void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) {} |
+ |
+ // SpdySettingsIR has a map for settings, so loses info about the order of |
+ // settings, and whether the same setting appeared more than once, so the |
+ // the actual settings (parameter and value) are provided in a vector. |
+ virtual void OnSettings(std::unique_ptr<SpdySettingsIR> frame, |
+ std::unique_ptr<SettingVector> settings) {} |
+ |
+ // A settings frame with an ACK has no content, but for uniformity passing |
+ // a frame with the ACK flag set. |
+ virtual void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame); |
+ |
+ virtual void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) {} |
+ |
+ // The SpdyFramer will not process any more data at this point. |
+ virtual void OnError(SpdyFramer* framer, SpdyTestDeframer* deframer) {} |
+}; |
+ |
+class SpdyTestDeframer : public SpdyFramerVisitorInterface { |
+ public: |
+ ~SpdyTestDeframer() override {} |
+ |
+ // Creates a SpdyFramerVisitorInterface that builds SpdyFrameIR concrete |
+ // instances based on the callbacks it receives; when an entire frame is |
+ // decoded/reconstructed it calls the passed in SpdyDeframerVisitorInterface. |
+ // Transfers ownership of visitor to the new SpdyTestDeframer, which ensures |
+ // that it continues to exist while the SpdyTestDeframer exists. |
+ static std::unique_ptr<SpdyTestDeframer> CreateConverter( |
+ std::unique_ptr<SpdyDeframerVisitorInterface> visitor); |
+ |
+ // Call to notify the deframer that the SpdyFramer has returned after reaching |
+ // the end of decoding a frame. This is used to flush info about some frame |
+ // types where we don't get a clear end signal; others are flushed (i.e. the |
+ // appropriate call to the SpdyDeframerVisitorInterface method is invoked) |
+ // as they're decoded by SpdyFramer and it calls the deframer. See the |
+ // example in the comments at the top of this file. |
+ virtual bool AtFrameEnd() = 0; |
+ |
+ protected: |
+ SpdyTestDeframer() {} |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(SpdyTestDeframer); |
+}; |
+ |
+// CollectedFrame holds the result of one call to SpdyDeframerVisitorInterface, |
+// as recorded by DeframerCallbackCollector. |
+struct CollectedFrame { |
+ CollectedFrame(); |
+ CollectedFrame(CollectedFrame&& other); |
+ ~CollectedFrame(); |
+ CollectedFrame& operator=(CollectedFrame&& other); |
+ |
+ // Compare a SpdyFrameIR sub-class instance, expected_ir, against the |
+ // collected SpdyFrameIR. |
+ template <class T, |
+ typename X = |
+ typename std::enable_if<std::is_base_of<SpdyFrameIR, T>::value>> |
+ ::testing::AssertionResult VerifyHasFrame(const T& expected_ir) const { |
+ return VerifySpdyFrameIREquals(expected_ir, frame_ir.get()) |
+ ? ::testing::AssertionSuccess() |
+ : ::testing::AssertionFailure(); |
+ } |
+ |
+ // Compare the collected headers against a StringPairVector. Ignores |
+ // this->frame_ir. |
+ ::testing::AssertionResult VerifyHasHeaders( |
+ const StringPairVector& expected_headers) const; |
+ |
+ // Compare the collected settings (parameter and value pairs) against |
+ // expected_settings. Ignores this->frame_ir. |
+ ::testing::AssertionResult VerifyHasSettings( |
+ const SettingVector& expected_settings) const; |
+ |
+ std::unique_ptr<SpdyFrameIR> frame_ir; |
+ std::unique_ptr<StringPairVector> headers; |
+ std::unique_ptr<SettingVector> settings; |
+ bool error_reported = false; |
+}; |
+ |
+// Creates a CollectedFrame instance for each callback, storing it in the |
+// vector provided to the constructor. |
+class DeframerCallbackCollector : public SpdyDeframerVisitorInterface { |
+ public: |
+ explicit DeframerCallbackCollector( |
+ std::vector<CollectedFrame>* collected_frames); |
+ ~DeframerCallbackCollector() override {} |
+ |
+ void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame_ir) override; |
+ void OnData(std::unique_ptr<SpdyDataIR> frame_ir) override; |
+ void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame_ir) override; |
+ void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame_ir, |
+ std::unique_ptr<StringPairVector> headers) override; |
+ void OnPing(std::unique_ptr<SpdyPingIR> frame_ir) override; |
+ void OnPingAck(std::unique_ptr<SpdyPingIR> frame_ir) override; |
+ void OnPriority(std::unique_ptr<SpdyPriorityIR> frame_ir) override; |
+ void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame_ir, |
+ std::unique_ptr<StringPairVector> headers) override; |
+ void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame_ir) override; |
+ void OnSettings(std::unique_ptr<SpdySettingsIR> frame_ir, |
+ std::unique_ptr<SettingVector> settings) override; |
+ void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame_ir) override; |
+ void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame_ir) override; |
+ void OnError(SpdyFramer* framer, SpdyTestDeframer* deframer) override; |
+ |
+ private: |
+ std::vector<CollectedFrame>* collected_frames_; |
+}; |
+ |
+} // namespace test |
+} // namespace net |
+ |
+#endif // NET_SPDY_SPDY_DEFRAMER_VISITOR_H_ |