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