proxygen
FilterTests.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
11 #include <limits>
18 #include <random>
19 
20 using namespace proxygen;
21 using namespace std;
22 using namespace testing;
23 
24 namespace {
25 const uint32_t kInitialCapacity = 12345;
26 }
27 
29  public:
30  MOCK_METHOD0(onConnectionSendWindowOpen, void());
31  MOCK_METHOD0(onConnectionSendWindowClosed, void());
32 };
33 
34 class FilterTest : public testing::Test {
35  public:
37  codec_(new MockHTTPCodec()),
38  chain_(unique_ptr<HTTPCodec>(codec_)) {
39  EXPECT_CALL(*codec_, setCallback(_))
40  .WillRepeatedly(SaveArg<0>(&callbackStart_));
41  chain_.setCallback(&callback_);
42  }
43  protected:
44 
50 };
51 
52 class HTTPChecksTest: public FilterTest {
53  public:
54  void SetUp() override {
55  chain_.add<HTTPChecks>();
56  }
57 };
58 
59 template <int initSize>
61  public:
62  void SetUp() override {
63  EXPECT_CALL(*codec_, getDefaultWindowSize())
64  .WillRepeatedly(Return(kInitialCapacity));
65 
66  if (initSize > kInitialCapacity) {
67  // If the initial size is bigger than the default, a window update
68  // will immediately be generated
69  EXPECT_CALL(*codec_, generateWindowUpdate(_, 0, initSize -
70  kInitialCapacity))
71  .WillOnce(InvokeWithoutArgs([this] () {
72  writeBuf_.append(makeBuf(10));
73  return 10;
74  }));
75  }
76  EXPECT_CALL(*codec_, generateBody(_, _, _, _, _))
77  .WillRepeatedly(Invoke([](folly::IOBufQueue& writeBuf,
78  HTTPCodec::StreamID /*stream*/,
79  std::shared_ptr<folly::IOBuf> chain,
80  folly::Optional<uint8_t> /*padding*/,
81  bool /*eom*/) {
82  auto len = chain->computeChainDataLength() + 4;
83  writeBuf.append(makeBuf(len));
84  return len;
85  }));
86  EXPECT_CALL(*codec_, isReusable()).WillRepeatedly(Return(true));
87 
88  // Construct flow control filter with capacity of 0, which will be
89  // overridden to the codec default, which is the minimum
90  filter_ = new FlowControlFilter(flowCallback_, writeBuf_, codec_,
91  initSize);
92  chain_.addFilters(std::unique_ptr<FlowControlFilter>(filter_));
93  }
96  int recvWindow_{initSize};
97 };
98 
101 
102 MATCHER(IsFlowException, "") {
103  return arg->hasCodecStatusCode() &&
104  arg->getCodecStatusCode() == ErrorCode::FLOW_CONTROL_ERROR &&
105  !arg->hasHttpStatusCode() &&
106  !arg->hasProxygenError();
107 }
108 
109 TEST_F(DefaultFlowControl, FlowControlConstruct) {
110  // Constructing the filter with a low capacity defaults to kInitialCapacity
111  // initial capacity, so no window update should have been generated in
112  // the constructor
113  InSequence enforceSequence;
114  ASSERT_EQ(writeBuf_.chainLength(), 0);
115 
116  // Our send window is limited to kInitialCapacity
117  chain_->generateBody(writeBuf_, 1,
118  makeBuf(kInitialCapacity - 1),
119  HTTPCodec::NoPadding, false);
120 
121  // the window isn't full yet, so getting a window update shouldn't give a
122  // callback informing us that it is open again
123  callbackStart_->onWindowUpdate(0, 1);
124 
125  // Now fill the window (2 more bytes)
126  EXPECT_CALL(flowCallback_, onConnectionSendWindowClosed());
127  chain_->generateBody(writeBuf_, 1, makeBuf(2), HTTPCodec::NoPadding, false);
128  // get the callback informing the window is open once we get a window update
129  EXPECT_CALL(flowCallback_, onConnectionSendWindowOpen());
130  callbackStart_->onWindowUpdate(0, 1);
131 
132  // Overflowing the window is fatal. Write 2 bytes (only 1 byte left in window)
133  EXPECT_DEATH_NO_CORE(chain_->generateBody(writeBuf_, 1, makeBuf(2),
134  HTTPCodec::NoPadding, false),
135  ".*");
136 }
137 
139  // Make sure we send a window update when the window decreases below half
140  InSequence enforceSequence;
141  EXPECT_CALL(callback_, onBody(_, _, _))
142  .WillRepeatedly(Return());
143 
144  // Have half the window outstanding
145  callbackStart_->onBody(1, makeBuf(kInitialCapacity / 2 + 1), 0);
146  filter_->ingressBytesProcessed(writeBuf_, kInitialCapacity / 2);
147 
148  // It should wait until the "+1" is ack'd to generate the coallesced update
150  generateWindowUpdate(_, 0, kInitialCapacity / 2 + 1));
151  filter_->ingressBytesProcessed(writeBuf_, 1);
152 }
153 
154 TEST_F(BigWindow, RecvTooMuch) {
155  // Constructing the filter with a large capacity causes a WINDOW_UPDATE
156  // for stream zero to be generated
157  ASSERT_GT(writeBuf_.chainLength(), 0);
158 
159  InSequence enforceSequence;
160  EXPECT_CALL(callback_, onBody(_, _, _));
161  EXPECT_CALL(callback_, onError(0, IsFlowException(), _))
162  .WillOnce(Invoke([](HTTPCodec::StreamID,
163  std::shared_ptr<HTTPException> exc,
164  bool /*newTxn*/) {
165  ASSERT_EQ(
166  "Failed to reserve receive window, window size=0, "
167  "amount=1",
168  std::string(exc->what()));
169  }));
170 
171  // Receive the max amount advertised
172  callbackStart_->onBody(1, makeBuf(recvWindow_), 0);
173  ASSERT_TRUE(chain_->isReusable());
174  // Receive 1 byte too much
175  callbackStart_->onBody(1, makeBuf(1), 0);
176  ASSERT_FALSE(chain_->isReusable());
177 }
178 
179 TEST_F(BigWindow, RemoteIncrease) {
180  // The remote side sends us a window update for stream=0, increasing our
181  // available window
182  InSequence enforceSequence;
183 
184  ASSERT_EQ(filter_->getAvailableSend(), kInitialCapacity);
185  callbackStart_->onWindowUpdate(0, 10);
186  ASSERT_EQ(filter_->getAvailableSend(), kInitialCapacity + 10);
187 
188  EXPECT_CALL(flowCallback_, onConnectionSendWindowClosed());
189  chain_->generateBody(writeBuf_, 1,
190  makeBuf(kInitialCapacity + 10),
191  HTTPCodec::NoPadding, false);
192  ASSERT_EQ(filter_->getAvailableSend(), 0);
193 
194  // Now the remote side sends a HUGE update (just barely legal)
195  // Since the window was full, this generates a callback from the filter
196  // telling us the window is no longer full.
197  EXPECT_CALL(flowCallback_, onConnectionSendWindowOpen());
198  callbackStart_->onWindowUpdate(0, std::numeric_limits<int32_t>::max());
199  ASSERT_EQ(filter_->getAvailableSend(), std::numeric_limits<int32_t>::max());
200 
201  // Now overflow it by 1
202  EXPECT_CALL(callback_, onError(0, IsFlowException(), _))
203  .WillOnce(Invoke([](HTTPCodec::StreamID,
204  std::shared_ptr<HTTPException> exc,
205  bool /*newTxn*/) {
206  ASSERT_EQ(
207  "Failed to update send window, outstanding=0, "
208  "amount=1",
209  std::string(exc->what()));
210  }));
211  callbackStart_->onWindowUpdate(0, 1);
212  ASSERT_FALSE(chain_->isReusable());
213 }
214 
215 TEST_F(HTTPChecksTest, SendTraceBodyDeath) {
216  // It is NOT allowed to send a TRACE with a body.
217 
218  HTTPMessage msg = getPostRequest();
219  msg.setMethod("TRACE");
220 
221  EXPECT_DEATH_NO_CORE(chain_->generateHeader(writeBuf_, 0, msg), ".*");
222 }
223 
224 TEST_F(HTTPChecksTest, SendGetBody) {
225  // It is allowed to send a GET with a content-length. It is up to the
226  // server to ignore it.
227 
228  EXPECT_CALL(*codec_, generateHeader(_, _, _, _, _));
229 
230  HTTPMessage msg = getPostRequest();
231  msg.setMethod("GET");
232 
233  chain_->generateHeader(writeBuf_, 0, msg);
234 }
235 
236 TEST_F(HTTPChecksTest, RecvTraceBody) {
237  // In proxygen, we deal with receiving a TRACE with a body by 400'ing it
238 
239  EXPECT_CALL(callback_, onError(_, _, _))
240  .WillOnce(Invoke([] (HTTPCodec::StreamID,
241  std::shared_ptr<HTTPException> exc,
242  bool newTxn) {
243  ASSERT_TRUE(newTxn);
244  ASSERT_EQ(exc->getHttpStatusCode(), 400);
245  ASSERT_EQ(0,
246  strcmp("RFC2616: Request Body Not Allowed",
247  exc->what()));
248  }));
249 
250  auto msg = makePostRequest();
251  msg->setMethod("TRACE");
252 
253  callbackStart_->onHeadersComplete(0, std::move(msg));
254 }
void SetUp() override
Definition: FilterTests.cpp:62
#define ASSERT_GT(val1, val2)
Definition: gtest.h:1976
std::unique_ptr< folly::IOBuf > makeBuf(uint32_t size)
Definition: TestUtils.cpp:36
static const folly::Optional< uint8_t > NoPadding
Definition: HTTPCodec.h:53
void append(std::unique_ptr< folly::IOBuf > &&buf, bool pack=false)
Definition: IOBufQueue.cpp:143
FlowControlFilter * filter_
Definition: FilterTests.cpp:95
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
LogLevel max
Definition: LogLevel.cpp:31
#define EXPECT_DEATH_NO_CORE(token, regex)
Definition: TestUtils.h:19
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
std::unique_ptr< Codec > codec_
std::unique_ptr< HTTPMessage > makePostRequest(uint32_t contentLength)
Definition: TestUtils.cpp:124
PolymorphicAction< internal::InvokeWithoutArgsAction< FunctionImpl > > InvokeWithoutArgs(FunctionImpl function_impl)
void writeBuf(const Buf &buf, folly::io::Appender &out)
static Options cacheChainLength()
Definition: IOBufQueue.h:83
PolymorphicAction< internal::InvokeAction< FunctionImpl > > Invoke(FunctionImpl function_impl)
MockHTTPCodecCallback callback_
Definition: FilterTests.cpp:48
void setMethod(HTTPMethod method)
HTTPCodec::Callback * callbackStart_
Definition: FilterTests.cpp:46
const char * string
Definition: Conv.cpp:212
MockHTTPCodec * codec_
Definition: FilterTests.cpp:45
HTTPMessage getPostRequest(uint32_t contentLength)
Definition: TestUtils.cpp:102
void SetUp() override
Definition: FilterTests.cpp:54
#define EXPECT_CALL(obj, call)
uint64_t StreamID
Definition: HTTPCodec.h:49
const internal::AnythingMatcher _
HTTPCodecFilterChain chain_
Definition: FilterTests.cpp:47
TEST_F(HeaderTableTests, IndexTranslation)
MATCHER(IsFlowException,"")
folly::Function< void()> callback_
#define ASSERT_FALSE(condition)
Definition: gtest.h:1868
StrictMock< MockFlowControlCallback > flowCallback_
Definition: FilterTests.cpp:94
#define ASSERT_TRUE(condition)
Definition: gtest.h:1865
internal::ReturnAction< R > Return(R value)
#define MOCK_METHOD0(m,...)