1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
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.
7  */
8 
9 #pragma once
10 
11 #include <folly/io/Cursor.h>
12 #include <folly/io/IOBufQueue.h>
13 #include <folly/io/async/EventBaseManager.h>
14 #include <limits>
15 #include <proxygen/lib/http/codec/HQFramer.h>
16 #include <proxygen/lib/http/codec/HQUnidirectionalCodec.h>
17 #include <proxygen/lib/http/codec/QPACKDecoderCodec.h>
18 #include <proxygen/lib/http/codec/QPACKEncoderCodec.h>
19 #include <proxygen/lib/http/session/HQDownstreamSession.h>
20 #include <proxygen/lib/http/session/HQUpstreamSession.h>
21 #include <proxygen/lib/http/session/test/HTTPSessionMocks.h>
22 #include <proxygen/lib/http/session/test/MockQuicSocketDriver.h>
23 #include <proxygen/lib/http/session/test/TestUtils.h>
24 
25 #define IS_H1Q_FB_V1 (GetParam().alpn_ == "h1q-fb")
26 #define IS_H1Q_FB_V2 (GetParam().alpn_ == "h1q-fb-v2")
27 #define IS_HQ (GetParam().alpn_.find("h3") == 0)
28 #define ALPN_H1Q_FB_V1 (alpn == "h1q-fb")
29 #define ALPN_H1Q_FB_V2 (alpn == "h1q-fb-v2")
30 #define ALPN_HQ (alpn.find("h3") == 0)
31 
32 namespace {
33 constexpr unsigned int kTransactionTimeout = 500;
34 constexpr unsigned int kConnectTimeout = 500;
35 constexpr size_t kQPACKTestDecoderMaxTableSize = 2048;
36 constexpr std::size_t kUnlimited = std::numeric_limits<std::size_t>::max();
37 const proxygen::hq::PushId kUnknownPushId =
38     std::numeric_limits<uint64_t>::max();
39 constexpr proxygen::hq::PushId kInitialPushId = 12345;
40 constexpr uint64_t kPushIdIncrement = 1;
41 constexpr uint64_t kDefaultUnidirStreamCredit = 3;
42 } // namespace
43 
44 struct TestParams {
45   std::string alpn_;
46   bool createQPACKStreams_{true};
47   bool shouldSendSettings_{true};
48   uint64_t unidirectionalStreamsCredit{kDefaultUnidirStreamCredit};
49   std::size_t numBytesOnPushStream{kUnlimited};
50   bool expectOnTransportReady{true};
51   bool datagrams_{false};
52 };
53 
54 std::string prBodyScriptToName(const std::vector<uint8_t>& bodyScript);
55 
56 size_t encodeQuicIntegerWithAtLeast(uint64_t value,
57                                     uint8_t atLeast,
58                                     folly::io::QueueAppender& appender);
59 
60 std::string paramsToTestName(const testing::TestParamInfo<TestParams>& info);
61 
62 size_t generateStreamPreface(folly::IOBufQueue& writeBuf,
63                              proxygen::hq::UnidirectionalStreamType type);
64 
65 folly::Optional<std::pair<proxygen::hq::UnidirectionalStreamType, size_t>>
66 parseStreamPreface(folly::io::Cursor& cursor, std::string alpn);
67 
68 void parseReadData(proxygen::hq::HQUnidirectionalCodec* codec,
69                    folly::IOBufQueue& readBuf,
70                    std::unique_ptr<folly::IOBuf> buf);
71 
72 void createControlStream(quic::MockQuicSocketDriver* socketDriver,
73                          quic::StreamId id,
74                          proxygen::hq::UnidirectionalStreamType streamType);
75 
76 class HQSessionTest
77     : public testing::TestWithParam<TestParams>
78     , public quic::MockQuicSocketDriver::LocalAppCallback
79     , public proxygen::hq::HQUnidirectionalCodec::Callback {
80 
81  public:
SetUp()82   void SetUp() override {
83     folly::EventBaseManager::get()->clearEventBase();
84     proxygen::HTTPSession::setDefaultWriteBufferLimit(65536);
85     proxygen::HTTP2PriorityQueue::setNodeLifetime(std::chrono::milliseconds(2));
86   }
TearDown()87   void TearDown() override {
88   }
89 
90  protected:
91   explicit HQSessionTest(
92       proxygen::TransportDirection direction,
93       folly::Optional<TestParams> overrideParams = folly::none)
direction_(direction)94       : direction_(direction),
95         overrideParams_(overrideParams),
96         qpackEncoderCodec_(qpackCodec_, *this),
97         qpackDecoderCodec_(qpackCodec_, *this),
98         controllerContainer_(GetParam())
99 
100   {
101     if (direction_ == proxygen::TransportDirection::DOWNSTREAM) {
102       hqSession_ = new proxygen::HQDownstreamSession(
103           std::chrono::milliseconds(kTransactionTimeout),
104           &controllerContainer_.mockController,
105           proxygen::mockTransportInfo,
106           nullptr,
107           nullptr);
108       nextUnidirectionalStreamId_ = 2;
109     } else if (direction_ == proxygen::TransportDirection::UPSTREAM) {
110       hqSession_ = new proxygen::HQUpstreamSession(
111           std::chrono::milliseconds(kTransactionTimeout),
112           std::chrono::milliseconds(kConnectTimeout),
113           &controllerContainer_.mockController,
114           proxygen::mockTransportInfo,
115           nullptr,
116           nullptr);
117       nextUnidirectionalStreamId_ = 3;
118     } else {
119       LOG(FATAL) << "wrong TransportEnum";
120     }
121 
122     if (GetParam().datagrams_) {
123       egressSettings_.setSetting(proxygen::SettingsId::_HQ_DATAGRAM, 1);
124     }
125 
126     if (!IS_H1Q_FB_V1) {
127       egressControlCodec_ = std::make_unique<proxygen::hq::HQControlCodec>(
128           nextUnidirectionalStreamId_,
129           direction_ == proxygen::TransportDirection::DOWNSTREAM
130               ? proxygen::TransportDirection::UPSTREAM
131               : proxygen::TransportDirection::DOWNSTREAM,
132           proxygen::hq::StreamDirection::EGRESS,
133           egressSettings_);
134     }
135     socketDriver_ = std::make_unique<quic::MockQuicSocketDriver>(
136         &eventBase_,
137         *hqSession_,
138         direction_ == proxygen::TransportDirection::DOWNSTREAM
139             ? quic::MockQuicSocketDriver::TransportEnum::SERVER
140             : quic::MockQuicSocketDriver::TransportEnum::CLIENT,
141         getProtocolString());
142 
143     hqSession_->setSocket(socketDriver_->getSocket());
144 
145     hqSession_->setEgressSettings(egressSettings_.getAllSettings());
146     qpackCodec_.setEncoderHeaderTableSize(1024);
147     qpackCodec_.setDecoderHeaderTableMaxSize(kQPACKTestDecoderMaxTableSize);
148     hqSession_->setInfoCallback(&infoCb_);
149 
150     socketDriver_->setMaxUniStreams(GetParam().unidirectionalStreamsCredit);
151 
152     EXPECT_CALL(infoCb_, onRead(testing::_, testing::_, testing::_))
153         .Times(testing::AnyNumber());
154     if (!IS_H1Q_FB_V1) {
155 
156       size_t ctrlStreamCount = (IS_H1Q_FB_V2 || IS_HQ) ? 1 : 0;
157       size_t qpackStreamCount =
158           (IS_HQ && GetParam().createQPACKStreams_) ? 2 : 0;
159       numCtrlStreams_ = ctrlStreamCount + qpackStreamCount;
160       socketDriver_->setLocalAppCallback(this);
161 
162       if (GetParam().unidirectionalStreamsCredit >= numCtrlStreams_) {
163         auto dirModifier =
164             (direction_ == proxygen::TransportDirection::DOWNSTREAM) ? 0 : 1;
165         EXPECT_CALL(infoCb_, onWrite(testing::_, testing::_))
166             .Times(testing::AtLeast(numCtrlStreams_));
167         for (auto i = 0; i < numCtrlStreams_; i++) {
168           folly::Optional<proxygen::HTTPCodec::StreamID> expectedStreamID =
169               i * 4 + 2 + dirModifier;
170           EXPECT_CALL(infoCb_, onRead(testing::_, testing::_, expectedStreamID))
171               .Times(testing::AtLeast(1));
172         }
173       }
174     }
175 
176     quic::QuicSocket::TransportInfo transportInfo;
177     transportInfo.srtt = std::chrono::microseconds(100);
178     transportInfo.congestionWindow = 1500;
179 
180     EXPECT_CALL(*socketDriver_->getSocket(), getTransportInfo())
181         .WillRepeatedly(testing::Return(transportInfo));
182   }
183 
createControlStreams()184   bool createControlStreams() {
185     // NOTE: this is NOT the stream credit advertised by the peer.
186     // this is the number of uni streams that we allow the peer to open. if that
187     // is not enough for the control streams, onTransportReady drops the
188     // connection, so don't try to create or write to new streams.
189     if (GetParam().unidirectionalStreamsCredit < numCtrlStreams_) {
190       return false;
191     }
192     if (IS_H1Q_FB_V2) {
193       connControlStreamId_ = nextUnidirectionalStreamId();
194       createControlStream(socketDriver_.get(),
195                           connControlStreamId_,
196                           proxygen::hq::UnidirectionalStreamType::H1Q_CONTROL);
197     } else if (IS_HQ) {
198       connControlStreamId_ = nextUnidirectionalStreamId();
199       createControlStream(socketDriver_.get(),
200                           connControlStreamId_,
201                           proxygen::hq::UnidirectionalStreamType::CONTROL);
202       if (GetParam().createQPACKStreams_) {
203         createControlStream(
204             socketDriver_.get(),
205             nextUnidirectionalStreamId(),
206             proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER);
207         createControlStream(
208             socketDriver_.get(),
209             nextUnidirectionalStreamId(),
210             proxygen::hq::UnidirectionalStreamType::QPACK_DECODER);
211       }
212       if (GetParam().shouldSendSettings_) {
213         sendSettings();
214       }
215     }
216     return true;
217   }
218 
sendSettings()219   void sendSettings() {
220     // For H1Q_FB_V2 we call this in some tests, but for V1 it would be an
221     // error
222     CHECK(!IS_H1Q_FB_V1);
223     folly::IOBufQueue writeBuf{folly::IOBufQueue::cacheChainLength()};
224     egressControlCodec_->generateSettings(writeBuf);
225     socketDriver_->addReadEvent(
226         connControlStreamId_, writeBuf.move(), std::chrono::milliseconds(0));
227   }
228 
getProtocolString()229   const std::string getProtocolString() const {
230     if (GetParam().alpn_ == "h3") {
231       return proxygen::kH3;
232     }
233     return GetParam().alpn_;
234   }
235 
readCallback(quic::StreamId id,std::unique_ptr<folly::IOBuf> buf)236   void readCallback(quic::StreamId id,
237                     std::unique_ptr<folly::IOBuf> buf) override {
238   }
239 
unidirectionalReadCallback(quic::StreamId id,std::unique_ptr<folly::IOBuf> buf)240   void unidirectionalReadCallback(quic::StreamId id,
241                                   std::unique_ptr<folly::IOBuf> buf) override {
242     // check for control streams
243     if (buf->empty()) {
244       return;
245     }
246 
247     auto it = controlStreams_.find(id);
248     if (it == controlStreams_.end()) {
249       folly::io::Cursor cursor(buf.get());
250       auto preface = parseStreamPreface(cursor, getProtocolString());
251       CHECK(preface) << "Preface can not be parsed protocolString="
252                      << getProtocolString();
253       switch (preface->first) {
254         case proxygen::hq::UnidirectionalStreamType::H1Q_CONTROL:
255         case proxygen::hq::UnidirectionalStreamType::CONTROL:
256           ingressControlCodec_ = std::make_unique<proxygen::hq::HQControlCodec>(
257               id,
258               proxygen::TransportDirection::UPSTREAM,
259               proxygen::hq::StreamDirection::INGRESS,
260               ingressSettings_,
261               preface->first);
262           ingressControlCodec_->setCallback(&httpCallbacks_);
263           break;
264         case proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER:
265         case proxygen::hq::UnidirectionalStreamType::QPACK_DECODER:
266           break;
267         case proxygen::hq::UnidirectionalStreamType::PUSH: {
268           auto pushIt = pushes_.find(id);
269           if (pushIt == pushes_.end()) {
270             auto pushId = quic::decodeQuicInteger(cursor);
271             if (pushId) {
272               pushes_.emplace(id, pushId->first);
273             }
274           }
275         }
276           return;
277         default:
278           CHECK(false) << "Unknown stream preface=" << preface->first;
279       }
280       socketDriver_->sock_->setControlStream(id);
281       auto res = controlStreams_.emplace(id, preface->first);
282       it = res.first;
283       buf->trimStart(preface->second);
284       if (buf->empty()) {
285         return;
286       }
287     }
288 
289     switch (it->second) {
290       case proxygen::hq::UnidirectionalStreamType::H1Q_CONTROL:
291       case proxygen::hq::UnidirectionalStreamType::CONTROL:
292         parseReadData(
293             ingressControlCodec_.get(), ingressControlBuf_, std::move(buf));
294         break;
295       case proxygen::hq::UnidirectionalStreamType::QPACK_ENCODER:
296         parseReadData(&qpackEncoderCodec_, encoderReadBuf_, std::move(buf));
297         break;
298       case proxygen::hq::UnidirectionalStreamType::QPACK_DECODER:
299         parseReadData(&qpackDecoderCodec_, decoderReadBuf_, std::move(buf));
300         break;
301       case proxygen::hq::UnidirectionalStreamType::PUSH:
302         VLOG(4) << "Ingress push streams should not go through "
303                 << "the unidirectional read path";
304         break;
305       default:
306         CHECK(false) << "Unknown stream type=" << it->second;
307     }
308   }
309 
onError(proxygen::HTTPCodec::StreamID streamID,const proxygen::HTTPException & error,bool)310   void onError(proxygen::HTTPCodec::StreamID streamID,
311                const proxygen::HTTPException& error,
312                bool /*newTxn*/) override {
313     LOG(FATAL) << __func__ << " streamID=" << streamID
314                << " error=" << error.what();
315   }
316 
nextUnidirectionalStreamId()317   quic::StreamId nextUnidirectionalStreamId() {
318     auto id = nextUnidirectionalStreamId_;
319     nextUnidirectionalStreamId_ += 4;
320     return id;
321   }
322 
323   struct MockControllerContainer {
MockControllerContainerMockControllerContainer324     explicit MockControllerContainer(TestParams params) {
325       testing::InSequence s;
326       EXPECT_CALL(mockController, attachSession(testing::_));
327       if (params.expectOnTransportReady) {
328         EXPECT_CALL(mockController, onTransportReady(testing::_));
329       }
330       EXPECT_CALL(mockController, detachSession(testing::_));
331     }
332     testing::StrictMock<proxygen::MockController> mockController;
333   };
334 
getMockController()335   testing::StrictMock<proxygen::MockController>& getMockController() {
336     return controllerContainer_.mockController;
337   }
338 
339  public:
getSocketDriver()340   quic::MockQuicSocketDriver* getSocketDriver() {
341     return socketDriver_.get();
342   }
343 
getSession()344   proxygen::HQSession* getSession() {
345     return hqSession_;
346   }
347 
setSessionDestroyCallback(folly::Function<void (const proxygen::HTTPSessionBase &)> cb)348   void setSessionDestroyCallback(
349       folly::Function<void(const proxygen::HTTPSessionBase&)> cb) {
350     EXPECT_CALL(infoCb_, onDestroy(testing::_))
351         .WillOnce(testing::Invoke(
352             [&](const proxygen::HTTPSessionBase&) { cb(*hqSession_); }));
353   }
354 
GetParam()355   const TestParams& GetParam() const {
356     if (overrideParams_) {
357       return *overrideParams_;
358     } else {
359       const testing::TestWithParam<TestParams>* base = this;
360       return base->GetParam();
361     }
362   }
363 
getH3Datagram(uint64_t streamId,std::unique_ptr<folly::IOBuf> datagram)364   std::unique_ptr<folly::IOBuf> getH3Datagram(
365       uint64_t streamId, std::unique_ptr<folly::IOBuf> datagram) {
366     // Prepend the H3 Datagram header to the datagram payload
367     // HTTP/3 Datagram {
368     //   Quarter Stream ID (i),
369     //   [Context ID (i)],
370     //   HTTP/3 Datagram Payload (..),
371     // }
372     quic::Buf headerBuf = quic::Buf(folly::IOBuf::create(0));
373     quic::BufAppender appender(headerBuf.get(),
374                                proxygen::kMaxDatagramHeaderSize);
375     auto streamIdRes = quic::encodeQuicInteger(
376         streamId / 4, [&](auto val) { appender.writeBE(val); });
377     if (streamIdRes.hasError()) {
378       return nullptr;
379     }
380     // Always use context-id = 0 for now
381     auto ctxIdRes =
382         quic::encodeQuicInteger(0, [&](auto val) { appender.writeBE(val); });
383     if (ctxIdRes.hasError()) {
384       return nullptr;
385     }
386     quic::BufQueue queue(std::move(headerBuf));
387     queue.append(std::move(datagram));
388     return queue.move();
389   }
390 
391  protected:
392   proxygen::TransportDirection direction_;
393   folly::Optional<TestParams> overrideParams_;
394   // Unidirectional Stream Codecs used for Ingress Only
395   proxygen::hq::QPACKEncoderCodec qpackEncoderCodec_;
396   proxygen::hq::QPACKDecoderCodec qpackDecoderCodec_;
397   // Read/WriteBufs for QPACKCodec, one for the encoder, one for the decoder
398   folly::IOBufQueue encoderReadBuf_{folly::IOBufQueue::cacheChainLength()};
399   folly::IOBufQueue decoderReadBuf_{folly::IOBufQueue::cacheChainLength()};
400   folly::IOBufQueue encoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};
401   folly::IOBufQueue decoderWriteBuf_{folly::IOBufQueue::cacheChainLength()};
402 
403   folly::EventBase eventBase_;
404   proxygen::HQSession* hqSession_;
405   MockControllerContainer controllerContainer_;
406   std::unique_ptr<quic::MockQuicSocketDriver> socketDriver_;
407   // One QPACKCodec per session, handles both encoder and decoder
408   proxygen::QPACKCodec qpackCodec_;
409   std::map<quic::StreamId, proxygen::hq::UnidirectionalStreamType>
410       controlStreams_;
411   // Ingress Control Stream
412   std::unique_ptr<proxygen::hq::HQControlCodec> ingressControlCodec_;
413   folly::IOBufQueue ingressControlBuf_{folly::IOBufQueue::cacheChainLength()};
414   proxygen::HTTPSettings egressSettings_{
415       {proxygen::SettingsId::HEADER_TABLE_SIZE, kQPACKTestDecoderMaxTableSize},
416       {proxygen::SettingsId::MAX_HEADER_LIST_SIZE, 655335},
417       {proxygen::SettingsId::_HQ_QPACK_BLOCKED_STREAMS, 100}};
418   proxygen::HTTPSettings ingressSettings_;
419   proxygen::FakeHTTPCodecCallback httpCallbacks_;
420   uint8_t numCtrlStreams_{0};
421   quic::StreamId connControlStreamId_;
422   testing::NiceMock<proxygen::MockHTTPSessionInfoCallback> infoCb_;
423   quic::StreamId nextUnidirectionalStreamId_;
424   // Egress Control Stream
425   std::unique_ptr<proxygen::hq::HQControlCodec> egressControlCodec_;
426   folly::F14FastMap<quic::StreamId, proxygen::hq::PushId> pushes_;
427 };
428