summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h
blob: 27535236aef505d0575bda8a3d0f8de5b13c9216 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// 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 QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
#define QUICHE_SPDY_CORE_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 = std::make_unique<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.
//    QuicheStringPiece 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 <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"

namespace spdy {
namespace test {

// Non-lossy representation of a SETTINGS frame payload.
typedef std::vector<std::pair<SpdyKnownSettingsId, 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(http2::Http2DecoderAdapter::SpdyFramerError /*error*/,
                       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() {}
  SpdyTestDeframer(const SpdyTestDeframer&) = delete;
  SpdyTestDeframer& operator=(const SpdyTestDeframer&) = delete;
};

// 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());
  }

  // 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(http2::Http2DecoderAdapter::SpdyFramerError error,
               SpdyTestDeframer* deframer) override;

 private:
  std::vector<CollectedFrame>* collected_frames_;
};

}  // namespace test
}  // namespace spdy

#endif  // QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_