Commit message: Fix h2 codec state after bad priority header.

Summary:
It's possible for the http2 codec to enter an invalid state after processing a http2 header with invalid priorities.
CVE-2018-6346

Reviewed By: maxgeorg

Differential Revision: D13510025

fbshipit-source-id: 7c4e42daf1cd2b912454d13a66ab8488d1863263
CVE: CVE-2018-6346
CWE: CWE-388
Vulnerability description: A potential denial-of-service issue in the Proxygen handling of invalid HTTP2 priority settings (specifically a circular dependency). This affects Proxygen prior to v2018.12.31.00.

Project: proxygen

TEST_F(HTTP2CodecTest, ResponseFromClient) {
  // this is to test EX_HEADERS frame, which carrys the HTTP response replied by
  // client side
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 1);
  proxygen::http2::writeSettings(
      output_, {{proxygen::SettingsId::ENABLE_EX_HEADERS, 1}});

  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");

  HTTPCodec::StreamID stream = folly::Random::rand32(10, 1024) * 2;
  HTTPCodec::StreamID controlStream = folly::Random::rand32(10, 1024) * 2 + 1;
  downstreamCodec_.generateExHeader(output_, stream, resp,
    HTTPCodec::ExAttributes(controlStream, true), true);

  parse();
  EXPECT_EQ(controlStream, callbacks_.controlStreamId);
  EXPECT_TRUE(callbacks_.isUnidirectional);
  EXPECT_EQ("OK", callbacks_.msg->getStatusMessage());
  callbacks_.expectMessage(true, 2, 200);
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ("OK", callbacks_.msg->getStatusMessage());
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
}

Project: proxygen

size_t HTTP2Codec::generateWindowUpdate(folly::IOBufQueue& writeBuf,
                                        StreamID stream,
                                        uint32_t delta) {
  VLOG(4) << "generating window update for stream=" << stream
          << ": Processed " << delta << " bytes";
  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "suppressed WINDOW_UPDATE for stream=" << stream
            << " ingressGoawayAck_=" << ingressGoawayAck_;
    return 0;
  }
  return generateHeaderCallbackWrapper(stream, http2::FrameType::WINDOW_UPDATE,
                                       http2::writeWindowUpdate(writeBuf, stream, delta));
}

Project: proxygen

TEST_F(HTTP2CodecTest, VirtualNodes) {
  DummyQueue queue;
  uint8_t level = 30;
  upstreamCodec_.addPriorityNodes(queue, output_, level);

  EXPECT_TRUE(parse());
  for (int i = 0; i < level; i++) {
    EXPECT_EQ(queue.nodes_[i], upstreamCodec_.mapPriorityToDependency(i));
  }

  // Out-of-range priorites are mapped to the lowest level of virtual nodes.
  EXPECT_EQ(queue.nodes_[level - 1],
            upstreamCodec_.mapPriorityToDependency(level));
  EXPECT_EQ(queue.nodes_[level - 1],
            upstreamCodec_.mapPriorityToDependency(level + 1));
}

Project: proxygen

TEST_F(HTTP2CodecTest, Trailers) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  upstreamCodec_.generateHeader(output_, 1, req);

  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  upstreamCodec_.generateBody(
      output_, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  upstreamCodec_.generateTrailers(output_, 1, trailers);

  parse();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, 5);
  EXPECT_EQ(callbacks_.trailers, 1);
  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, JunkAfterConnError) {
  HTTPMessage req = getGetRequest();
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");

  // write headers frame for stream 0
  writeFrameHeaderManual(output_, 0, (uint8_t)http2::FrameType::HEADERS, 0, 0);
  // now write a valid headers frame, should never be parsed
  upstreamCodec_.generateHeader(output_, 1, req);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

ErrorCode HTTP2Codec::parseWindowUpdate(Cursor& cursor) {
  VLOG(4) << "parsing WINDOW_UPDATE frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  uint32_t delta = 0;
  auto err = http2::parseWindowUpdate(cursor, curHeader_, delta);
  RETURN_IF_ERROR(err);
  if (delta == 0) {
    VLOG(4) << "Invalid 0 length delta for stream=" << curHeader_.stream;
    if (curHeader_.stream == 0) {
      goawayErrorMessage_ = folly::to<string>(
        "GOAWAY error: invalid/0 length delta for streamID=",
        curHeader_.stream);
      return ErrorCode::PROTOCOL_ERROR;
    } else {
      // Parsing a zero delta window update should cause a protocol error
      // and send a rst stream
      goawayErrorMessage_ = folly::to<string>(
        "parseWindowUpdate Invalid 0 length");
      VLOG(4) << goawayErrorMessage_;
      streamError(folly::to<std::string>("streamID=", curHeader_.stream,
                                         " with HTTP2Codec stream error: ",
                                         "window update delta=", delta),
                  ErrorCode::PROTOCOL_ERROR);
      return ErrorCode::PROTOCOL_ERROR;
    }
  }
  // if window exceeds 2^31-1, connection/stream error flow control error
  // must be checked in session/txn
  deliverCallbackIfAllowed(&HTTPCodec::Callback::onWindowUpdate,
                           "onWindowUpdate", curHeader_.stream, delta);
  return ErrorCode::NO_ERROR;
}

Project: proxygen

void HTTP2Codec::generatePushPromise(folly::IOBufQueue& writeBuf,
                                     StreamID stream,
                                     const HTTPMessage& msg,
                                     StreamID assocStream,
                                     bool eom,
                                     HTTPHeaderSize* size) {
  generateHeaderImpl(writeBuf,
                     stream,
                     msg,
                     assocStream,
                     folly::none, /* controlStream */
                     eom,
                     size);
}

Project: proxygen

  void SetUp() override {
    HTTPParallelCodecTest::SetUp();
  }

Project: proxygen

TEST_F(HTTP2CodecTest, BadSettings) {
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE, 0xffffffff);
  upstreamCodec_.generateSettings(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

void HTTP2Codec::requestUpgrade(HTTPMessage& request) {
  static folly::ThreadLocalPtr<HTTP2Codec> defaultCodec;
  if (!defaultCodec.get()) {
    defaultCodec.reset(new HTTP2Codec(TransportDirection::UPSTREAM));
  }

  auto& headers = request.getHeaders();
  headers.set(HTTP_HEADER_UPGRADE, http2::kProtocolCleartextString);
  if (!request.checkForHeaderToken(HTTP_HEADER_CONNECTION, "Upgrade", false)) {
    headers.add(HTTP_HEADER_CONNECTION, "Upgrade");
  }
  IOBufQueue writeBuf{IOBufQueue::cacheChainLength()};
  defaultCodec->generateSettings(writeBuf);
  // fake an ack since defaultCodec gets reused
  defaultCodec->handleSettingsAck();
  writeBuf.trimStart(http2::kFrameHeaderSize);
  auto buf = writeBuf.move();
  buf->coalesce();
  headers.set(http2::kProtocolSettingsHeader,
              base64url_encode(folly::ByteRange(buf->data(), buf->length())));
  if (!request.checkForHeaderToken(HTTP_HEADER_CONNECTION,
                                   http2::kProtocolSettingsHeader.c_str(),
                                   false)) {
    headers.add(HTTP_HEADER_CONNECTION, http2::kProtocolSettingsHeader);
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, IgnoreExHeadersSetting) {
  // disable EX_HEADERS on egress
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 0);
  auto ptr = downstreamCodec_.getEgressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(0, ptr->value);

  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(nullptr, ptr);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());

  // attempt to enable EX_HEADERS on ingress
  http2::writeSettings(output_,
                      {SettingPair(SettingsId::ENABLE_EX_HEADERS, 1)});
  parse();

  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(nullptr, ptr);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());

  // attempt to disable EX_HEADERS on ingress
  callbacks_.reset();
  http2::writeSettings(output_,
                      {SettingPair(SettingsId::ENABLE_EX_HEADERS, 0)});
  parse();

  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(nullptr, ptr);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());
}

Project: proxygen

TEST_F(HTTP2CodecTest, SettingsAck) {
  upstreamCodec_.generateSettingsAck(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 0);
  EXPECT_EQ(callbacks_.settingsAcks, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, ExHeadersWithPriority) {
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 1);
  proxygen::http2::writeSettings(
      output_, {{proxygen::SettingsId::ENABLE_EX_HEADERS, 1}});

  auto req = getGetRequest();
  auto pri = HTTPMessage::HTTPPriority(0, false, 7);
  req.setHTTP2Priority(pri);
  upstreamCodec_.generateExHeader(output_, 3, req,
                                  HTTPCodec::ExAttributes(1, true));

  parse();
  EXPECT_EQ(callbacks_.msg->getHTTP2Priority(), pri);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersReplyWithPseudoHeaders) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  downstreamCodec_.generateHeader(output_, 1, resp);

  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  downstreamCodec_.generateBody(
      output_, 1, std::move(buf), HTTPCodec::NoPadding, false);

  HPACKCodec headerCodec(TransportDirection::DOWNSTREAM);
  std::string post("POST");
  std::vector<proxygen::compress::Header> trailers = {
      Header::makeHeaderForTest(headers::kMethod, post)};
  auto encodedTrailers = headerCodec.encode(trailers);
  http2::writeHeaders(output_,
                      std::move(encodedTrailers),
                      1,
                      folly::none,
                      http2::kNoPadding,
                      true,
                      true);
  parseUpstream();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.trailers, 0);
  EXPECT_EQ(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

std::string base64url_decode(const std::string& str) {
  return proxygen::Base64::urlDecode(str);
}

Project: proxygen

void HTTP2CodecTest::testFrameSizeLimit(bool oversized) {
  HTTPMessage req = getBigGetRequest("/guacamole");
  auto settings = downstreamCodec_.getEgressSettings();

  parse(); // consume preface
  if (oversized) {
    // trick upstream for sending a 2x bigger HEADERS frame
    settings->setSetting(SettingsId::MAX_FRAME_SIZE,
                         http2::kMaxFramePayloadLengthMin * 2);
    downstreamCodec_.generateSettings(output_);
    parseUpstream();
  }

  settings->setSetting(SettingsId::MAX_FRAME_SIZE,
                       http2::kMaxFramePayloadLengthMin);
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  parse();
  // session error
  EXPECT_EQ(callbacks_.messageBegin, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.headersComplete, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.messageComplete, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, oversized ? 1 : 0);
}

Project: proxygen

void HTTP2Codec::onHeader(const folly::fbstring& name,
                          const folly::fbstring& value) {
  if (decodeInfo_.onHeader(name, value)) {
    if (name == "user-agent" && userAgent_.empty()) {
      userAgent_ = value.toStdString();
    }
  } else {
    VLOG(4) << "dir=" << uint32_t(transportDirection_) <<
      decodeInfo_.parsingError << " codec=" << headerCodec_;
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersNoBody) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  upstreamCodec_.generateHeader(output_, 1, req);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  upstreamCodec_.generateTrailers(output_, 1, trailers);

  parse();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 0);
  EXPECT_EQ(callbacks_.bodyLength, 0);
  EXPECT_EQ(callbacks_.trailers, 1);
  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 2);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, IgnoreUnknownSettings) {
  auto numSettings = downstreamCodec_.getIngressSettings()->getNumSettings();
  std::deque<SettingPair> settings;
  for (uint32_t i = 200; i < (200 + 1024); i++) {
    settings.push_back(SettingPair(SettingsId(i), i));
  }
  http2::writeSettings(output_, settings);
  parse();

  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_EQ(numSettings,
            downstreamCodec_.getIngressSettings()->getNumSettings());
}

Project: proxygen

ErrorCode HTTP2Codec::handleSettings(const std::deque<SettingPair>& settings) {
  SettingsList settingsList;
  for (auto& setting: settings) {
    switch (setting.first) {
      case SettingsId::HEADER_TABLE_SIZE:
      {
        uint32_t tableSize = setting.second;
        if (setting.second > http2::kMaxHeaderTableSize) {
          VLOG(2) << "Limiting table size from " << tableSize << " to " <<
            http2::kMaxHeaderTableSize;
          tableSize = http2::kMaxHeaderTableSize;
        }
        headerCodec_.setEncoderHeaderTableSize(tableSize);
      }
      break;
      case SettingsId::ENABLE_PUSH:
        if ((setting.second != 0 && setting.second != 1) ||
            (setting.second == 1 &&
             transportDirection_ == TransportDirection::UPSTREAM)) {
          goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: ENABLE_PUSH invalid setting=", setting.second,
              " for streamID=", curHeader_.stream);
          VLOG(4) << goawayErrorMessage_;
          return ErrorCode::PROTOCOL_ERROR;
        }
        break;
      case SettingsId::MAX_CONCURRENT_STREAMS:
        break;
      case SettingsId::INITIAL_WINDOW_SIZE:
        if (setting.second > http2::kMaxWindowUpdateSize) {
          goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: INITIAL_WINDOW_SIZE invalid size=", setting.second,
              " for streamID=", curHeader_.stream);
          VLOG(4) << goawayErrorMessage_;
          return ErrorCode::PROTOCOL_ERROR;
        }
        break;
      case SettingsId::MAX_FRAME_SIZE:
        if (setting.second < http2::kMaxFramePayloadLengthMin ||
            setting.second > http2::kMaxFramePayloadLength) {
          goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: MAX_FRAME_SIZE invalid size=", setting.second,
              " for streamID=", curHeader_.stream);
          VLOG(4) << goawayErrorMessage_;
          return ErrorCode::PROTOCOL_ERROR;
        }
        ingressSettings_.setSetting(SettingsId::MAX_FRAME_SIZE, setting.second);
        break;
      case SettingsId::MAX_HEADER_LIST_SIZE:
        break;
      case SettingsId::ENABLE_EX_HEADERS:
      {
        auto ptr = egressSettings_.getSetting(SettingsId::ENABLE_EX_HEADERS);
        if (ptr && ptr->value > 0) {
          VLOG(4) << getTransportDirectionString(getTransportDirection())
                  << " got ENABLE_EX_HEADERS=" << setting.second;
          if (setting.second != 0 && setting.second != 1) {
            goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: invalid ENABLE_EX_HEADERS=", setting.second,
              " for streamID=", curHeader_.stream);
            VLOG(4) << goawayErrorMessage_;
            return ErrorCode::PROTOCOL_ERROR;
          }
          break;
        } else {
          // egress ENABLE_EX_HEADERS is disabled, consider the ingress
          // ENABLE_EX_HEADERS as unknown setting, and ignore it.
          continue;
        }
      }
      case SettingsId::ENABLE_CONNECT_PROTOCOL:
        if (setting.second > 1) {
          goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: ENABLE_CONNECT_PROTOCOL invalid number=",
              setting.second, " for streamID=", curHeader_.stream);
          VLOG(4) << goawayErrorMessage_;
          return ErrorCode::PROTOCOL_ERROR;
        }
        break;
      case SettingsId::THRIFT_CHANNEL_ID:
      case SettingsId::THRIFT_CHANNEL_ID_DEPRECATED:
        break;
      case SettingsId::SETTINGS_HTTP_CERT_AUTH:
        break;
      default:
        continue; // ignore unknown setting
    }
    ingressSettings_.setSetting(setting.first, setting.second);
    settingsList.push_back(*ingressSettings_.getSetting(setting.first));
  }
  if (callback_) {
    callback_->onSettings(settingsList);
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadConnect) {
  std::string v1 = "CONNECT";
  std::string v2 = "somehost:576";
  std::vector<proxygen::compress::Header> goodHeaders = {
    Header::makeHeaderForTest(headers::kMethod, v1),
    Header::makeHeaderForTest(headers::kAuthority, v2),
  };

  // See https://tools.ietf.org/html/rfc7540#section-8.3
  std::string v3 = "/foobar";
  std::vector<proxygen::compress::Header> badHeaders = {
    Header::makeHeaderForTest(headers::kScheme, headers::kHttp),
    Header::makeHeaderForTest(headers::kPath, v3),
  };

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  HTTPCodec::StreamID stream = 1;

  for (size_t i = 0; i < badHeaders.size(); i++, stream += 2) {
    auto allHeaders = goodHeaders;
    allHeaders.push_back(badHeaders[i]);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, badHeaders.size());
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, DoubleGoaway) {
  parse();
  SetUpUpstreamTest();
  downstreamCodec_.generateGoaway(output_, std::numeric_limits<int32_t>::max(),
                                  ErrorCode::NO_ERROR);
  EXPECT_TRUE(downstreamCodec_.isWaitingToDrain());
  EXPECT_TRUE(downstreamCodec_.isReusable());
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(2));
  downstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);
  EXPECT_FALSE(downstreamCodec_.isWaitingToDrain());
  EXPECT_FALSE(downstreamCodec_.isReusable());
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(2));

  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(2));
  parseUpstream();
  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(2));
  EXPECT_EQ(callbacks_.goaways, 2);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);

  upstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);
  EXPECT_TRUE(upstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_FALSE(upstreamCodec_.isStreamIngressEgressAllowed(2));
  parse();
  EXPECT_TRUE(downstreamCodec_.isStreamIngressEgressAllowed(0));
  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(1));
  EXPECT_FALSE(downstreamCodec_.isStreamIngressEgressAllowed(2));
}

Project: proxygen

size_t HTTP2Codec::generateRstStream(folly::IOBufQueue& writeBuf,
                                     StreamID stream,
                                     ErrorCode statusCode) {
  VLOG(4) << "sending RST_STREAM for stream=" << stream
          << " with code=" << getErrorCodeString(statusCode);
  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "suppressed RST_STREAM for stream=" << stream
            << " ingressGoawayAck_=" << ingressGoawayAck_;
    return 0;
  }
  // Suppress any EOM callback for the current frame.
  if (stream == curHeader_.stream) {
    curHeader_.flags &= ~http2::END_STREAM;
    pendingEndStreamHandling_ = false;
    ingressWebsocketUpgrade_ = false;
  }
  upgradedStreams_.erase(stream);

  if (statusCode == ErrorCode::PROTOCOL_ERROR) {
    VLOG(2) << "sending RST_STREAM with code=" << getErrorCodeString(statusCode)
            << " for stream=" << stream << " user-agent=" << userAgent_;
  }
  auto code = http2::errorCodeToReset(statusCode);
  return generateHeaderCallbackWrapper(stream, http2::FrameType::RST_STREAM,
                                       http2::writeRstStream(writeBuf, stream, code));
}

Project: proxygen

TEST_F(HTTP2CodecTest, DuplicateBadHeaderPriority) {
  // Sent an initial header with a circular dependency
  HTTPMessage req = getGetRequest();
  req.setHTTP2Priority(HTTPMessage::HTTPPriority(0, false, 7));
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  // Hack ingress with circular dependency.
  EXPECT_TRUE(parse([&](IOBuf* ingress) {
    folly::io::RWPrivateCursor c(ingress);
    c.skip(http2::kFrameHeaderSize + http2::kConnectionPreface.length());
    c.writeBE<uint32_t>(1);
  }));

  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);

  // On the same stream, send another request.
  HTTPMessage nextRequest = getGetRequest();
  upstreamCodec_.generateHeader(output_, 1, nextRequest, true /* eom */);
  parse();
  EXPECT_EQ(callbacks_.streamErrors, 2);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

  HTTP2CodecTest()
    :HTTPParallelCodecTest(upstreamCodec_, downstreamCodec_) {}

Project: proxygen

ErrorCode HTTP2Codec::parseAllData(Cursor& cursor) {
  std::unique_ptr<IOBuf> outData;
  uint16_t padding = 0;
  VLOG(10) << "parsing all frame DATA bytes for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  auto ret = http2::parseData(cursor, curHeader_, outData, padding);
  RETURN_IF_ERROR(ret);

  if (callback_ && (padding > 0 || (outData && !outData->empty()))) {
    if (!outData) {
      outData = std::make_unique<IOBuf>();
    }
    deliverCallbackIfAllowed(&HTTPCodec::Callback::onBody, "onBody",
                             curHeader_.stream, std::move(outData), padding);
  }
  return handleEndStream();
}

Project: proxygen

ErrorCode HTTP2Codec::parseContinuation(Cursor& cursor) {
  std::unique_ptr<IOBuf> headerBuf;
  VLOG(4) << "parsing CONTINUATION frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  auto err = http2::parseContinuation(cursor, curHeader_, headerBuf);
  RETURN_IF_ERROR(err);
  err = parseHeadersImpl(cursor, std::move(headerBuf),
                         folly::none, folly::none, folly::none);
  return err;
}

Project: proxygen

TEST_F(HTTP2CodecTest, DataFramePartialDataWithNoAppByte) {
  const size_t bufSize = 10;
  auto buf = makeBuf(bufSize);
  const size_t padding = 10;
  upstreamCodec_.generateBody(output_, 1, buf->clone(), padding, true);
  EXPECT_EQ(output_.chainLength(), 54);

  auto ingress = output_.move();
  ingress->coalesce();
  // Copy up to the padding length byte to a new buffer
  auto ingress1 = IOBuf::copyBuffer(ingress->data(), 34);
  size_t parsed = downstreamCodec_.onIngress(*ingress1);
  // The 34th byte is the padding length byte which should not be parsed
  EXPECT_EQ(parsed, 33);
  // Copy from the padding length byte to the end
  auto ingress2 = IOBuf::copyBuffer(ingress->data() + 33, 21);
  parsed = downstreamCodec_.onIngress(*ingress2);
  // The padding length byte should be parsed this time along with 10 bytes of
  // application data and 10 bytes of padding
  EXPECT_EQ(parsed, 21);

  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, bufSize);
  // Total padding is the padding length byte and the padding bytes
  EXPECT_EQ(callbacks_.paddingBytes, padding + 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), buf->moveToFbString());
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicConnect) {
  std::string authority = "myhost:1234";
  HTTPMessage request;
  request.setMethod(HTTPMethod::CONNECT);
  request.getHeaders().add(proxygen::HTTP_HEADER_HOST, authority);
  upstreamCodec_.generateHeader(output_, 1, request, false /* eom */);

  parse();
  callbacks_.expectMessage(false, 1, "");
  EXPECT_EQ(HTTPMethod::CONNECT, callbacks_.msg->getMethod());
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ(authority, headers.getSingleOrEmpty(proxygen::HTTP_HEADER_HOST));
}

Project: proxygen

TEST_F(HTTP2CodecTest, IgnoreExHeadersIfNotEnabled) {
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 0);

  HTTPMessage req = getGetRequest("/guacamole");
  downstreamCodec_.generateExHeader(output_, 3, req,
                                    HTTPCodec::ExAttributes(1, true));

  parse();
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

folly::Optional<ErrorCode> HTTP2Codec::parseHeadersCheckConcurrentStreams(
    const folly::Optional<http2::PriorityUpdate>& priority) {
  if (curHeader_.type == http2::FrameType::HEADERS ||
      curHeader_.type == http2::FrameType::EX_HEADERS) {
    if (curHeader_.flags & http2::PRIORITY) {
      DCHECK(priority);
      // callback_->onPriority(priority.get());
    }

    // callback checks total number of streams is smaller than settings max
    if (callback_->numIncomingStreams() >=
        egressSettings_.getSetting(SettingsId::MAX_CONCURRENT_STREAMS,
                                   std::numeric_limits<int32_t>::max())) {
      streamError(folly::to<string>("Exceeded max_concurrent_streams"),
                  ErrorCode::REFUSED_STREAM, true);
      return ErrorCode::NO_ERROR;
    }
  }
  return folly::Optional<ErrorCode>();
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadHeadersReply) {
  static const std::string v1("200");
  static const vector<proxygen::compress::Header> respHeaders = {
    Header::makeHeaderForTest(headers::kStatus, v1),
  };

  HPACKCodec headerCodec(TransportDirection::DOWNSTREAM);
  HTTPCodec::StreamID stream = 1;
  // missing fields (missing authority is OK)
  for (size_t i = 0; i < respHeaders.size(); i++, stream += 2) {
    std::vector<proxygen::compress::Header> allHeaders = respHeaders;
    allHeaders.erase(allHeaders.begin() + i);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }
  // dup fields
  std::string v("foomonkey");
  for (size_t i = 0; i < respHeaders.size(); i++, stream += 2) {
    std::vector<proxygen::compress::Header> allHeaders = respHeaders;
    auto h = allHeaders[i];
    h.value = &v;
    allHeaders.push_back(h);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 2);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, NoAppByte) {
  const uint8_t noAppByte[] = {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
                               0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
                               0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a,
                               0x00, 0x00, 0x56, 0x00, 0x5d, 0x00, 0x00, 0x00,
                               0x01, 0x55, 0x00};
  output_.clear();
  output_.append(noAppByte, sizeof(noAppByte));
  EXPECT_EQ(output_.chainLength(), sizeof(noAppByte));

  EXPECT_TRUE(parse());
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseFrame(folly::io::Cursor& cursor) {
  FOLLY_SCOPED_TRACE_SECTION("HTTP2Codec - parseFrame");
  if (expectedContinuationStream_ != 0 &&
       (curHeader_.type != http2::FrameType::CONTINUATION ||
        expectedContinuationStream_ != curHeader_.stream)) {
    goawayErrorMessage_ = folly::to<string>(
        "GOAWAY error: while expected CONTINUATION with stream=",
        expectedContinuationStream_, ", received streamID=", curHeader_.stream,
        " of type=", getFrameTypeString(curHeader_.type));
    VLOG(4) << goawayErrorMessage_;
    return ErrorCode::PROTOCOL_ERROR;
  }
  if (expectedContinuationStream_ == 0 &&
      curHeader_.type == http2::FrameType::CONTINUATION) {
    goawayErrorMessage_ = folly::to<string>(
        "GOAWAY error: unexpected CONTINUATION received with streamID=",
        curHeader_.stream);
    VLOG(4) << goawayErrorMessage_;
    return ErrorCode::PROTOCOL_ERROR;
  }
  if (frameAffectsCompression(curHeader_.type) &&
      curHeaderBlock_.chainLength() + curHeader_.length >
      egressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE, 0)) {
    // this may be off by up to the padding length (max 255), but
    // these numbers are already so generous, and we're comparing the
    // max-uncompressed to the actual compressed size.  Let's fail
    // before buffering.

    // TODO(t6513634): it would be nicer to stream-process this header
    // block to keep the connection state consistent without consuming
    // memory, and fail just the request per the HTTP/2 spec (section
    // 10.3)
    goawayErrorMessage_ = folly::to<string>(
      "Failing connection due to excessively large headers");
    LOG(ERROR) << goawayErrorMessage_;
    return ErrorCode::PROTOCOL_ERROR;
  }

  expectedContinuationStream_ =
    (frameAffectsCompression(curHeader_.type) &&
     !(curHeader_.flags & http2::END_HEADERS)) ? curHeader_.stream : 0;

  switch (curHeader_.type) {
    case http2::FrameType::DATA:
      return parseAllData(cursor);
    case http2::FrameType::HEADERS:
      return parseHeaders(cursor);
    case http2::FrameType::PRIORITY:
      return parsePriority(cursor);
    case http2::FrameType::RST_STREAM:
      return parseRstStream(cursor);
    case http2::FrameType::SETTINGS:
      return parseSettings(cursor);
    case http2::FrameType::PUSH_PROMISE:
      return parsePushPromise(cursor);
    case http2::FrameType::EX_HEADERS:
      if (ingressSettings_.getSetting(SettingsId::ENABLE_EX_HEADERS, 0)) {
        return parseExHeaders(cursor);
      } else {
        VLOG(2) << "EX_HEADERS not enabled, ignoring the frame";
        break;
      }
    case http2::FrameType::PING:
      return parsePing(cursor);
    case http2::FrameType::GOAWAY:
      return parseGoaway(cursor);
    case http2::FrameType::WINDOW_UPDATE:
      return parseWindowUpdate(cursor);
    case http2::FrameType::CONTINUATION:
      return parseContinuation(cursor);
    case http2::FrameType::ALTSVC:
      // fall through, unimplemented
      break;
    case http2::FrameType::CERTIFICATE_REQUEST:
      return parseCertificateRequest(cursor);
    case http2::FrameType::CERTIFICATE:
      return parseCertificate(cursor);
    default:
      // Implementations MUST ignore and discard any frame that has a
      // type that is unknown
      break;
  }

  // Landing here means unknown, unimplemented or ignored frame.
  VLOG(2) << "Skipping frame (type=" << (uint8_t)curHeader_.type << ")";
  cursor.skip(curHeader_.length);
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, HTTP2SettingsSuccess) {
  HTTPMessage req = getGetRequest("/guacamole");

  // empty settings
  req.getHeaders().add(http2::kProtocolSettingsHeader, "");
  EXPECT_TRUE(downstreamCodec_.onIngressUpgradeMessage(req));

  // real settings (overwrites empty)
  HTTP2Codec::requestUpgrade(req);
  EXPECT_TRUE(downstreamCodec_.onIngressUpgradeMessage(req));
}

Project: proxygen

TEST_F(HTTP2CodecTest, OversizedHeader) {
  testHeaderListSize(true);
}

Project: proxygen

void HTTP2Codec::onHeadersComplete(HTTPHeaderSize decodedSize,
                                   bool /*acknowledge*/) {
  decodeInfo_.onHeadersComplete(decodedSize);
  decodeInfo_.msg->setAdvancedProtocolString(http2::kProtocolString);

  HTTPMessage* msg = decodeInfo_.msg.get();
  HTTPRequestVerifier& verifier = decodeInfo_.verifier;
  if ((transportDirection_ == TransportDirection::DOWNSTREAM) &&
      verifier.hasUpgradeProtocol() &&
      (*msg->getUpgradeProtocol() == headers::kWebsocketString) &&
      msg->getMethod() == HTTPMethod::CONNECT) {
    msg->setIngressWebsocketUpgrade();
    ingressWebsocketUpgrade_ = true;
  } else {
    auto it = upgradedStreams_.find(curHeader_.stream);
    if (it != upgradedStreams_.end()) {
      upgradedStreams_.erase(curHeader_.stream);
      // a websocket upgrade was sent on this stream.
      if (msg->getStatusCode() != 200) {
        decodeInfo_.parsingError =
          folly::to<string>("Invalid response code to a websocket upgrade: ",
                            msg->getStatusCode());
        return;
      }
      msg->setIngressWebsocketUpgrade();
    }
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicWindow) {
  // This test would fail if the codec had window state
  upstreamCodec_.generateWindowUpdate(output_, 0, 10);
  upstreamCodec_.generateWindowUpdate(output_, 0, http2::kMaxWindowUpdateSize);
  upstreamCodec_.generateWindowUpdate(output_, 1, 12);
  upstreamCodec_.generateWindowUpdate(output_, 1, http2::kMaxWindowUpdateSize);

  parse();
  EXPECT_EQ(callbacks_.windowUpdateCalls, 4);
  EXPECT_EQ(callbacks_.windowUpdates[0],
            std::vector<uint32_t>({10, http2::kMaxWindowUpdateSize}));
  EXPECT_EQ(callbacks_.windowUpdates[1],
            std::vector<uint32_t>({12, http2::kMaxWindowUpdateSize}));
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parsePushPromise(Cursor& cursor) {
  // stream id must be idle - protocol error
  // assoc-stream-id=closed/unknown - protocol error, unless rst_stream sent

  /*
   * What does "must handle" mean in the following context?  I have to
   * accept this as a valid pushed resource?

    However, an endpoint that has sent RST_STREAM on the associated
    stream MUST handle PUSH_PROMISE frames that might have been
    created before the RST_STREAM frame is received and processed.
  */
  if (transportDirection_ != TransportDirection::UPSTREAM) {
    goawayErrorMessage_ = folly::to<string>(
      "Received PUSH_PROMISE on DOWNSTREAM codec");
    VLOG(2) << goawayErrorMessage_;
    return ErrorCode::PROTOCOL_ERROR;
  }
  if (egressSettings_.getSetting(SettingsId::ENABLE_PUSH, -1) != 1) {
    goawayErrorMessage_ = folly::to<string>(
      "Received PUSH_PROMISE on codec with push disabled");
    VLOG(2) << goawayErrorMessage_;
    return ErrorCode::PROTOCOL_ERROR;
  }
  VLOG(4) << "parsing PUSH_PROMISE frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  uint32_t promisedStream;
  std::unique_ptr<IOBuf> headerBlockFragment;
  auto err = http2::parsePushPromise(cursor, curHeader_, promisedStream,
                                     headerBlockFragment);
  RETURN_IF_ERROR(err);
  RETURN_IF_ERROR(checkNewStream(promisedStream, false /* trailersAllowed */));
  err = parseHeadersImpl(cursor, std::move(headerBlockFragment), folly::none,
                         promisedStream, folly::none);
  return err;
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersContinuation) {
  HTTPMessage req = getGetRequest("/guacamole");
  upstreamCodec_.generateHeader(output_, 1, req);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  trailers.add("x-huge-trailer",
               std::string(http2::kMaxFramePayloadLengthMin, '!'));
  upstreamCodec_.generateTrailers(output_, 1, trailers);

  parse();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_NE(callbacks_.msg, nullptr);
  EXPECT_EQ(callbacks_.trailers, 1);
  EXPECT_NE(callbacks_.msg->getTrailers(), nullptr);
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
  EXPECT_EQ(std::string(http2::kMaxFramePayloadLengthMin, '!'),
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-huge-trailer"));
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 3);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicRst) {
  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::ENHANCE_YOUR_CALM);
  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.bodyCalls, 0);
  EXPECT_EQ(callbacks_.aborts, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadHeaders) {
  static const std::string v1("GET");
  static const std::string v2("/");
  static const std::string v3("http");
  static const std::string v4("foo.com");
  static const vector<proxygen::compress::Header> reqHeaders = {
    Header::makeHeaderForTest(headers::kMethod, v1),
    Header::makeHeaderForTest(headers::kPath, v2),
    Header::makeHeaderForTest(headers::kScheme, v3),
    Header::makeHeaderForTest(headers::kAuthority, v4),
  };

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  HTTPCodec::StreamID stream = 1;
  // missing fields (missing authority is OK)
  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {
    std::vector<proxygen::compress::Header> allHeaders = reqHeaders;
    allHeaders.erase(allHeaders.begin() + i);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }
  // dup fields
  std::string v("foomonkey");
  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {
    std::vector<proxygen::compress::Header> allHeaders = reqHeaders;
    auto h = allHeaders[i];
    h.value = &v;
    allHeaders.push_back(h);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 7);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, GoawayReply) {
  upstreamCodec_.generateGoaway(output_, 0, ErrorCode::NO_ERROR);

  parse();
  EXPECT_EQ(callbacks_.goaways, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);

  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  downstreamCodec_.generateHeader(output_, 1, resp);
  downstreamCodec_.generateEOM(output_, 1);
  parseUpstream();
  callbacks_.expectMessage(true, 1, 200);
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
}

Project: proxygen

bool HTTP2Codec::onIngressUpgradeMessage(const HTTPMessage& msg) {
  if (!HTTPParallelCodec::onIngressUpgradeMessage(msg)) {
    return false;
  }
  if (msg.getHeaders().getNumberOfValues(http2::kProtocolSettingsHeader) != 1) {
    VLOG(4) << __func__ << " with no HTTP2-Settings";
    return false;
  }

  const auto& settingsHeader = msg.getHeaders().getSingleOrEmpty(
    http2::kProtocolSettingsHeader);
  if (settingsHeader.empty()) {
    return true;
  }

  auto decoded = base64url_decode(settingsHeader);

  // Must be well formed Base64Url and not too large
  if (decoded.empty() || decoded.length() > http2::kMaxFramePayloadLength) {
    VLOG(4) << __func__ << " failed to decode HTTP2-Settings";
    return false;
  }
  std::unique_ptr<IOBuf> decodedBuf = IOBuf::wrapBuffer(decoded.data(),
                                                        decoded.length());
  IOBufQueue settingsQueue{IOBufQueue::cacheChainLength()};
  settingsQueue.append(std::move(decodedBuf));
  Cursor c(settingsQueue.front());
  std::deque<SettingPair> settings;
  // downcast is ok because of above length check
  http2::FrameHeader frameHeader{
    (uint32_t)settingsQueue.chainLength(), 0, http2::FrameType::SETTINGS, 0, 0};
  auto err = http2::parseSettings(c, frameHeader, settings);
  if (err != ErrorCode::NO_ERROR) {
    VLOG(4) << __func__ << " bad settings frame";
    return false;
  }

  if (handleSettings(settings) != ErrorCode::NO_ERROR) {
    VLOG(4) << __func__ << " handleSettings failed";
    return false;
  }

  return true;
}

Project: proxygen

TEST_F(HTTP2CodecTest, LongData) {
  // Hack the max frame size artificially low
  HTTPSettings* settings = (HTTPSettings*)upstreamCodec_.getIngressSettings();
  settings->setSetting(SettingsId::MAX_FRAME_SIZE, 16);
  auto buf = makeBuf(100);
  upstreamCodec_.generateBody(output_, 1, buf->clone(), HTTPCodec::NoPadding,
                              true);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 7);
  EXPECT_EQ(callbacks_.bodyLength, 100);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), buf->moveToFbString());
}

Project: proxygen

TEST_F(HTTP2CodecTest, MalformedPadding) {
  const uint8_t badInput[] = {
    0x00, 0x00, 0x0d, 0x01, 0xbe, 0x63, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x73,
    0x00, 0x00, 0x06, 0x08, 0x72, 0x00, 0x24, 0x00, 0xfa, 0x4d, 0x0d
  };
  output_.append(badInput, sizeof(badInput));

  EXPECT_FALSE(parse());
}

Project: proxygen

size_t HTTP2Codec::generatePingRequest(folly::IOBufQueue& writeBuf) {
  // should probably let the caller specify when integrating with session
  // we know HTTPSession sets up events to track ping latency
  uint64_t opaqueData = folly::Random::rand64();
  VLOG(4) << "Generating ping request with opaqueData=" << opaqueData;
  return generateHeaderCallbackWrapper(0, http2::FrameType::PING,
                                       http2::writePing(writeBuf, opaqueData, false /* no ack */));
}

Project: proxygen

size_t HTTP2Codec::generateSettings(folly::IOBufQueue& writeBuf) {
  std::deque<SettingPair> settings;
  for (auto& setting: egressSettings_.getAllSettings()) {
    switch (setting.id) {
      case SettingsId::HEADER_TABLE_SIZE:
        if (pendingTableMaxSize_) {
          LOG(ERROR) << "Can't have more than one settings in flight, skipping";
          continue;
        } else {
          pendingTableMaxSize_ = setting.value;
        }
        break;
      case SettingsId::ENABLE_PUSH:
        if (transportDirection_ == TransportDirection::DOWNSTREAM) {
          // HTTP/2 spec says downstream must not send this flag
          // HTTP2Codec uses it to determine if push features are enabled
          continue;
        } else {
          CHECK(setting.value == 0 || setting.value == 1);
        }
        break;
      case SettingsId::MAX_CONCURRENT_STREAMS:
      case SettingsId::INITIAL_WINDOW_SIZE:
      case SettingsId::MAX_FRAME_SIZE:
        break;
      case SettingsId::MAX_HEADER_LIST_SIZE:
        headerCodec_.setMaxUncompressed(setting.value);
        break;
      case SettingsId::ENABLE_EX_HEADERS:
        CHECK(setting.value == 0 || setting.value == 1);
        if (setting.value == 0) {
          continue; // just skip the experimental setting if disabled
        } else {
          VLOG(4) << "generating ENABLE_EX_HEADERS=" << setting.value;
        }
        break;
      case SettingsId::ENABLE_CONNECT_PROTOCOL:
        if (setting.value == 0) {
          continue;
        }
        break;
      case SettingsId::THRIFT_CHANNEL_ID:
      case SettingsId::THRIFT_CHANNEL_ID_DEPRECATED:
        break;
      default:
        LOG(ERROR) << "ignore unknown settingsId="
                   << std::underlying_type<SettingsId>::type(setting.id)
                   << " value=" << setting.value;
        continue;
    }

    settings.push_back(SettingPair(setting.id, setting.value));
  }
  VLOG(4) << getTransportDirectionString(getTransportDirection())
          << " generating " << (unsigned)settings.size() << " settings";
  return generateHeaderCallbackWrapper(0, http2::FrameType::SETTINGS,
                                       http2::writeSettings(writeBuf, settings));
}

Project: proxygen

TEST_F(HTTP2CodecTest, NormalSizeHeader) {
  testHeaderListSize(false);
}

Project: proxygen

size_t HTTP2Codec::generateBody(folly::IOBufQueue& writeBuf,
                                StreamID stream,
                                std::unique_ptr<folly::IOBuf> chain,
                                folly::Optional<uint8_t> padding,
                                bool eom) {
  // todo: generate random padding for everything?
  size_t written = 0;
  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "Suppressing DATA for stream=" << stream << " ingressGoawayAck_="
            << ingressGoawayAck_;
    return 0;
  }
  IOBufQueue queue(IOBufQueue::cacheChainLength());
  queue.append(std::move(chain));
  size_t maxFrameSize = maxSendFrameSize();
  while (queue.chainLength() > maxFrameSize) {
    auto chunk = queue.split(maxFrameSize);
    written += generateHeaderCallbackWrapper(
                  stream,
                  http2::FrameType::DATA,
                  http2::writeData(writeBuf,
                                   std::move(chunk),
                                   stream,
                                   padding,
                                   false,
                                   reuseIOBufHeadroomForData_));
  }

  return written + generateHeaderCallbackWrapper(
                      stream,
                      http2::FrameType::DATA,
                      http2::writeData(writeBuf,
                                       queue.move(),
                                       stream,
                                       padding,
                                       eom,
                                       reuseIOBufHeadroomForData_));
}

Project: proxygen

TEST_F(HTTP2CodecTest, NoExHeaders) {
  // do not emit ENABLE_EX_HEADERS setting, if disabled
  SetUpUpstreamTest();

  EXPECT_EQ(callbacks_.settings, 0);
  EXPECT_EQ(callbacks_.numSettings, 0);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());

  parseUpstream();

  EXPECT_EQ(callbacks_.settings, 1);
  // only 3 standard settings: HEADER_TABLE_SIZE, ENABLE_PUSH, MAX_FRAME_SIZE.
  EXPECT_EQ(callbacks_.numSettings, 3);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());
}

Project: proxygen

void HTTP2Codec::generateHeader(folly::IOBufQueue& writeBuf,
                                StreamID stream,
                                const HTTPMessage& msg,
                                bool eom,
                                HTTPHeaderSize* size) {
  generateHeaderImpl(writeBuf,
                     stream,
                     msg,
                     folly::none, /* assocStream */
                     folly::none, /* controlStream */
                     eom,
                     size);
}

Project: proxygen

TEST_F(HTTP2CodecTest, InvalidExHeadersSetting) {
  // enable EX_HEADERS on egress
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 1);

  // attempt to set a invalid ENABLE_EX_HEADERS value
  http2::writeSettings(output_,
                      {SettingPair(SettingsId::ENABLE_EX_HEADERS, 110)});
  parse();

  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

TEST(HTTP2CodecConstantsTest, HTTPContantsAreCommonHeaders) {
  // The purpose of this test is to verify some basic assumptions that should
  // never change but to make clear that the following http2 header constants
  // map to the respective common headers.  Should this test ever fail, the
  // H2Codec would need to be updated in the corresponding places when creating
  // compress/Header objects.
  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kMethod),
            HTTP_HEADER_COLON_METHOD);
  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kScheme),
            HTTP_HEADER_COLON_SCHEME);
  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kPath),
            HTTP_HEADER_COLON_PATH);
  EXPECT_EQ(
    HTTPCommonHeaders::hash(headers::kAuthority),
    HTTP_HEADER_COLON_AUTHORITY);
  EXPECT_EQ(HTTPCommonHeaders::hash(headers::kStatus),
            HTTP_HEADER_COLON_STATUS);
}

Project: proxygen

size_t HTTP2Codec::generateEOM(folly::IOBufQueue& writeBuf,
                               StreamID stream) {
  VLOG(4) << "sending EOM for stream=" << stream;
  upgradedStreams_.erase(stream);
  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "suppressed EOM for stream=" << stream << " ingressGoawayAck_="
            << ingressGoawayAck_;
    return 0;
  }
  return generateHeaderCallbackWrapper(
            stream,
            http2::FrameType::DATA,
            http2::writeData(writeBuf,
                             nullptr,
                             stream,
                             http2::kNoPadding,
                             true,
                             reuseIOBufHeadroomForData_));
}

Project: proxygen

TEST_F(HTTP2CodecTest, DataFramePartialDataOnFrameHeaderCall) {
  using namespace testing;
  NiceMock<MockHTTPCodecCallback> mockCallback;
  EXPECT_CALL(mockCallback, onFrameHeader(_, _, _, _, _));

  const size_t bufSize = 10;
  auto buf = makeBuf(bufSize);
  const size_t padding = 10;
  upstreamCodec_.generateBody(output_, 1, buf->clone(), padding, true);
  EXPECT_EQ(output_.chainLength(), 54);

  downstreamCodec_.setCallback(&mockCallback);

  auto ingress = output_.move();
  ingress->coalesce();
  // Copy partial byte to a new buffer
  auto ingress1 = IOBuf::copyBuffer(ingress->data(), 34);
  downstreamCodec_.onIngress(*ingress1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, EnableExHeadersSetting) {
  // enable EX_HEADERS on egress
  downstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 1);

  auto ptr = downstreamCodec_.getEgressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(1, ptr->value);

  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(nullptr, ptr);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());

  // attempt to enable EX_HEADERS on ingress
  http2::writeSettings(output_,
                      {SettingPair(SettingsId::ENABLE_EX_HEADERS, 1)});
  parse();

  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(1, ptr->value);
  EXPECT_EQ(true, downstreamCodec_.supportsExTransactions());

  // attempt to disable EX_HEADERS on ingress
  callbacks_.reset();
  http2::writeSettings(output_,
                      {SettingPair(SettingsId::ENABLE_EX_HEADERS, 0)});
  parse();

  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  ptr = downstreamCodec_.getIngressSettings()->getSetting(
      SettingsId::ENABLE_EX_HEADERS);
  EXPECT_EQ(0, ptr->value);
  EXPECT_EQ(false, downstreamCodec_.supportsExTransactions());
}

Project: proxygen

void HTTP2Codec::onDecodeError(HPACK::DecodeError decodeError) {
  decodeInfo_.decodeError = decodeError;
}

Project: proxygen

  ~DummyQueue() override {}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicGoaway) {
  std::unique_ptr<folly::IOBuf> debugData =
    folly::IOBuf::copyBuffer("debugData");
  upstreamCodec_.generateGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM,
                                std::move(debugData));

  parse();
  EXPECT_EQ(callbacks_.goaways, 1);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), "debugData");
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseRstStream(Cursor& cursor) {
  // rst for stream in idle state - protocol error
  VLOG(4) << "parsing RST_STREAM frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  upgradedStreams_.erase(curHeader_.stream);
  ErrorCode statusCode = ErrorCode::NO_ERROR;
  auto err = http2::parseRstStream(cursor, curHeader_, statusCode);
  RETURN_IF_ERROR(err);
  if (statusCode == ErrorCode::PROTOCOL_ERROR) {
    goawayErrorMessage_ = folly::to<string>(
        "GOAWAY error: RST_STREAM with code=", getErrorCodeString(statusCode),
        " for streamID=", curHeader_.stream, " user-agent=", userAgent_);
    VLOG(2) << goawayErrorMessage_;
  }
  deliverCallbackIfAllowed(&HTTPCodec::Callback::onAbort, "onAbort",
                           curHeader_.stream, statusCode);
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, Cookies) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add("Cookie", "chocolate-chip=1");
  req.getHeaders().add("Cookie", "rainbow-chip=2");
  req.getHeaders().add("Cookie", "butterscotch=3");
  req.getHeaders().add("Cookie", "oatmeal-raisin=4");
  req.setSecure(true);
  upstreamCodec_.generateHeader(output_, 1, req);

  parse();
  callbacks_.expectMessage(false, 2, "/guacamole");
  EXPECT_EQ(callbacks_.msg->getCookie("chocolate-chip"), "1");
  EXPECT_EQ(callbacks_.msg->getCookie("rainbow-chip"), "2");
  EXPECT_EQ(callbacks_.msg->getCookie("butterscotch"), "3");
  EXPECT_EQ(callbacks_.msg->getCookie("oatmeal-raisin"), "4");
}

Project: proxygen

size_t HTTP2Codec::generateChunkTerminator(folly::IOBufQueue& /*writeBuf*/,
                                           StreamID /*stream*/) {
  // HTTP/2 has no chunk terminators
  return 0;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicRstInvalidCode) {
  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::_SPDY_INVALID_STREAM);
  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.bodyCalls, 0);
  EXPECT_EQ(callbacks_.aborts, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, SettingsTableSize) {
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 8192);
  upstreamCodec_.generateSettings(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  downstreamCodec_.generateSettingsAck(output_);
  parseUpstream();

  callbacks_.reset();

  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  SetUpUpstreamTest();
  downstreamCodec_.generateHeader(output_, 1, resp);

  parseUpstream();
  callbacks_.expectMessage(false, 2, 200);
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
}

Project: proxygen

size_t HTTP2Codec::generateCertificate(folly::IOBufQueue& writeBuf,
                                       uint16_t certId,
                                       std::unique_ptr<folly::IOBuf> certData) {
  size_t written = 0;
  VLOG(4) << "sending CERTIFICATE with Cert-ID=" << certId << "for stream=0";
  IOBufQueue queue(IOBufQueue::cacheChainLength());
  queue.append(std::move(certData));
  // The maximum size of an autenticator fragment, combined with the Cert-ID can
  // not exceed the maximal allowable size of a sent frame.
  size_t maxChunkSize = maxSendFrameSize() - sizeof(certId);
  while (queue.chainLength() > maxChunkSize) {
    auto chunk = queue.splitAtMost(maxChunkSize);
    written +=
        http2::writeCertificate(writeBuf, certId, std::move(chunk), true);
  }
  return written +
         http2::writeCertificate(writeBuf, certId, queue.move(), false);
}

Project: proxygen

  DummyQueue() {}

Project: proxygen

ErrorCode HTTP2Codec::parsePing(Cursor& cursor) {
  VLOG(4) << "parsing PING frame length=" << curHeader_.length;
  uint64_t opaqueData = 0;
  auto err = http2::parsePing(cursor, curHeader_, opaqueData);
  RETURN_IF_ERROR(err);
  if (callback_) {
    if (curHeader_.flags & http2::ACK) {
      callback_->onPingReply(opaqueData);
    } else {
      callback_->onPingRequest(opaqueData);
    }
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicPushPromise) {
  upstreamCodec_.generateSettings(output_);
  parse();
  EXPECT_FALSE(upstreamCodec_.supportsPushTransactions());
  EXPECT_FALSE(downstreamCodec_.supportsPushTransactions());

  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::ENABLE_PUSH, 1);
  upstreamCodec_.generateSettings(output_);
  parse();
  EXPECT_TRUE(upstreamCodec_.supportsPushTransactions());
  EXPECT_TRUE(downstreamCodec_.supportsPushTransactions());

  SetUpUpstreamTest();

  HTTPCodec::StreamID assocStream = 7;
  for (auto i = 0; i < 2; i++) {
    // Push promise
    HTTPCodec::StreamID pushStream = downstreamCodec_.createStream();
    HTTPMessage req = getGetRequest();
    req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
    downstreamCodec_.generatePushPromise(output_, pushStream, req, assocStream);

    parseUpstream();
    callbacks_.expectMessage(false, 2, "/"); // + host
    EXPECT_EQ(callbacks_.assocStreamId, assocStream);
    EXPECT_EQ(callbacks_.headersCompleteId, pushStream);
    auto& headers = callbacks_.msg->getHeaders();
    EXPECT_EQ("coolio", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));
    callbacks_.reset();

    // Actual reply headers
    HTTPMessage resp;
    resp.setStatusCode(200);
    resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "text/plain");
    downstreamCodec_.generateHeader(output_, pushStream, resp);

    parseUpstream();
    callbacks_.expectMessage(false, 2, 200);
    EXPECT_EQ(callbacks_.headersCompleteId, pushStream);
    EXPECT_EQ(callbacks_.assocStreamId, 0);
    EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
    EXPECT_EQ("text/plain", callbacks_.msg->getHeaders().getSingleOrEmpty(
                  HTTP_HEADER_CONTENT_TYPE));
    callbacks_.reset();
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, UnknownFrameType) {
  HTTPMessage req = getGetRequest();
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");

  // unknown frame type 17
  writeFrameHeaderManual(output_, 17, 37, 0, 1);
  output_.append("wicked awesome!!!");
  upstreamCodec_.generateHeader(output_, 1, req);

  parse();
  callbacks_.expectMessage(false, 2, ""); // + host
}

Project: proxygen

TEST_F(HTTP2CodecTest, NormalSizeFrame) {
  testFrameSizeLimit(false);
}

Project: proxygen

ErrorCode HTTP2Codec::parseSettings(Cursor& cursor) {
  VLOG(4) << "parsing SETTINGS frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  std::deque<SettingPair> settings;
  auto err = http2::parseSettings(cursor, curHeader_, settings);
  RETURN_IF_ERROR(err);
  if (curHeader_.flags & http2::ACK) {
    handleSettingsAck();
    return ErrorCode::NO_ERROR;
  }
  return handleSettings(settings);
}

Project: proxygen

TEST_F(HTTP2CodecTest, CleartextUpgrade) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  HTTP2Codec::requestUpgrade(req);
  EXPECT_EQ(req.getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE), "h2c");
  EXPECT_TRUE(req.checkForHeaderToken(HTTP_HEADER_CONNECTION,
                                      "Upgrade", false));
  EXPECT_TRUE(req.checkForHeaderToken(
                HTTP_HEADER_CONNECTION,
                http2::kProtocolSettingsHeader.c_str(), false));
  EXPECT_GT(
    req.getHeaders().getSingleOrEmpty(http2::kProtocolSettingsHeader).length(),
    0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseCertificateRequest(Cursor& cursor) {
  VLOG(4) << "parsing CERTIFICATE_REQUEST frame length=" << curHeader_.length;
  uint16_t requestId = 0;
  std::unique_ptr<IOBuf> authRequest;

  auto err = http2::parseCertificateRequest(
      cursor, curHeader_, requestId, authRequest);
  RETURN_IF_ERROR(err);
  if (callback_) {
    callback_->onCertificateRequest(requestId, std::move(authRequest));
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, TestMultipleIdenticalContentLengthHeaders) {
  // Generate a POST request with two Content-Length headers
  // NOTE: getPostRequest already adds the content-length
  HTTPMessage req = getPostRequest();
  req.getHeaders().add("content-length", "200");
  EXPECT_EQ(req.getHeaders().getNumberOfValues("content-length"), 2);

  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);
  parse();

  // Check that the headers parsing completes correctly
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.headersComplete, 1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadSettingsTableSize) {
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 8192);
  // This sets the max decoder table size to 8k
  upstreamCodec_.generateSettings(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);

  callbacks_.reset();

  // Attempt to set a new max table size.  This is a no-op because the first,
  // setting is unacknowledged.  The upstream encoder will up the table size to
  // 8k per the first settings frame and the HPACK codec will send a code to
  // update the decoder.
  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 4096);

  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  SetUpUpstreamTest();
  downstreamCodec_.generateHeader(output_, 1, resp);

  parseUpstream();
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadContinuationStream) {
  HTTPMessage req = getBigGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req);

  // empirically determined the size of continuation frame, and fake it
  output_.trimEnd(http2::kFrameHeaderSize + 4134);
  auto fakeHeaders = makeBuf(4134);
  http2::writeContinuation(output_, 3, true, std::move(fakeHeaders));

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 2);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, WebsocketIncorrectResponse) {
  parse();
  SetUpUpstreamTest();
  parseUpstream();

  output_.clear();
  HTTPMessage req = getGetRequest("/apples");
  req.setSecure(true);
  req.setEgressWebsocketUpgrade();
  upstreamCodec_.generateHeader(output_, 1, req, false);
  parse();

  output_.clear();
  HTTPMessage resp;
  resp.setStatusCode(201);
  resp.setStatusMessage("OK");
  downstreamCodec_.generateHeader(output_, 1, resp);
  parseUpstream();
  EXPECT_EQ(callbacks_.streamErrors, 1);
}

Project: proxygen

ErrorCode HTTP2Codec::parseCertificate(Cursor& cursor) {
  VLOG(4) << "parsing CERTIFICATE frame length=" << curHeader_.length;
  uint16_t certId = 0;
  std::unique_ptr<IOBuf> authData;
  auto err = http2::parseCertificate(cursor, curHeader_, certId, authData);
  RETURN_IF_ERROR(err);
  if (curAuthenticatorBlock_.empty()) {
    curCertId_ = certId;
  } else if (certId != curCertId_) {
    // Received CERTIFICATE frame with different Cert-ID.
    return ErrorCode::PROTOCOL_ERROR;
  }
  curAuthenticatorBlock_.append(std::move(authData));
  if (curAuthenticatorBlock_.chainLength() > http2::kMaxAuthenticatorBufSize) {
    // Received excessively long authenticator.
    return ErrorCode::PROTOCOL_ERROR;
  }
  if (!(curHeader_.flags & http2::TO_BE_CONTINUED)) {
    auto authenticator = curAuthenticatorBlock_.move();
    if (callback_) {
      callback_->onCertificate(certId, std::move(authenticator));
    } else {
      curAuthenticatorBlock_.clear();
    }
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadGoaway) {
  std::unique_ptr<folly::IOBuf> debugData =
    folly::IOBuf::copyBuffer("debugData");
  upstreamCodec_.generateGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM,
                                std::move(debugData));
  EXPECT_DEATH_NO_CORE(upstreamCodec_.generateGoaway(
                         output_, 27, ErrorCode::ENHANCE_YOUR_CALM), ".*");
}

Project: proxygen

TEST_F(HTTP2CodecTest, TestAllEgressFrameTypeCallbacks) {
  class CallbackTypeTracker {
    std::set<uint8_t> types;
  public:
    void add(uint8_t, uint8_t type, uint64_t, uint16_t) {
      types.insert(type);
    }

    bool isAllFrameTypesReceived() {
      http2::FrameType expectedTypes[] = {
        http2::FrameType::DATA,
        http2::FrameType::HEADERS,
        http2::FrameType::PRIORITY,
        http2::FrameType::RST_STREAM,
        http2::FrameType::SETTINGS,
        http2::FrameType::PUSH_PROMISE,
        http2::FrameType::PING,
        http2::FrameType::GOAWAY,
        http2::FrameType::WINDOW_UPDATE,
        http2::FrameType::CONTINUATION,
        http2::FrameType::EX_HEADERS,
      };

      for(http2::FrameType type: expectedTypes) {
        EXPECT_TRUE(types.find(static_cast<uint8_t>(type)) != types.end())
          << "callback missing for type " << static_cast<uint8_t>(type);
      }
      return types.size() == (sizeof(expectedTypes)/sizeof(http2::FrameType));
    }
  };

  CallbackTypeTracker callbackTypeTracker;

  NiceMock<MockHTTPCodecCallback> mockCallback;
  upstreamCodec_.setCallback(&mockCallback);
  downstreamCodec_.setCallback(&mockCallback);
  EXPECT_CALL(mockCallback, onGenerateFrameHeader(_, _, _, _)).
    WillRepeatedly(Invoke(&callbackTypeTracker, &CallbackTypeTracker::add));

  // DATA frame
  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  upstreamCodec_.generateBody(output_, 2, std::move(buf),
                              HTTPCodec::NoPadding, true);

  HTTPHeaderSize size;
  size.uncompressed = size.compressed = 0;
  HTTPMessage req = getGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req, true, &size);

  upstreamCodec_.generatePriority(output_, 3,
                                  HTTPMessage::HTTPPriority(0, true, 1));
  upstreamCodec_.generateRstStream(output_, 2, ErrorCode::ENHANCE_YOUR_CALM);
  upstreamCodec_.generateSettings(output_);
  downstreamCodec_.generatePushPromise(output_, 2, req, 1);
  upstreamCodec_.generatePingRequest(output_);

  std::unique_ptr<folly::IOBuf> debugData =
      folly::IOBuf::copyBuffer("debugData");
  upstreamCodec_.generateGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM,
                                std::move(debugData));

  upstreamCodec_.generateWindowUpdate(output_, 0, 10);

  HTTPCodec::StreamID stream = folly::Random::rand32(10, 1024) * 2;
  HTTPCodec::StreamID controlStream = folly::Random::rand32(10, 1024) * 2 + 1;
  downstreamCodec_.generateExHeader(output_, stream, req,
    HTTPCodec::ExAttributes(controlStream, true));

  // Tests the continuation frame
  req = getBigGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  EXPECT_TRUE(callbackTypeTracker.isAllFrameTypesReceived());
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadPushPromise) {
  // ENABLE_PUSH is now 0 by default
  SetUpUpstreamTest();
  HTTPMessage req = getGetRequest();
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  downstreamCodec_.generatePushPromise(output_, 2, req, 1);

  parseUpstream();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.assocStreamId, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

void HTTP2Codec::handleSettingsAck() {
  if (pendingTableMaxSize_) {
    headerCodec_.setDecoderHeaderTableMaxSize(*pendingTableMaxSize_);
    pendingTableMaxSize_ = folly::none;
  }
  if (callback_) {
    callback_->onSettingsAck();
  }
}

Project: proxygen

size_t HTTP2Codec::generatePingReply(folly::IOBufQueue& writeBuf,
                                     uint64_t uniqueID) {
  VLOG(4) << "Generating ping reply with opaqueData=" << uniqueID;
  return generateHeaderCallbackWrapper(0, http2::FrameType::PING,
                                       http2::writePing(writeBuf, uniqueID, true /* ack */));
}

Project: proxygen

HTTP2Codec::~HTTP2Codec() {}

Project: proxygen

ErrorCode HTTP2Codec::handleEndStream() {
  if (curHeader_.type != http2::FrameType::HEADERS &&
      curHeader_.type != http2::FrameType::EX_HEADERS &&
      curHeader_.type != http2::FrameType::CONTINUATION &&
      curHeader_.type != http2::FrameType::DATA) {
    return ErrorCode::NO_ERROR;
  }

  // do we need to handle case where this stream has already aborted via
  // another callback (onHeadersComplete/onBody)?
  pendingEndStreamHandling_ |= (curHeader_.flags & http2::END_STREAM);

  // with a websocket upgrade, we need to send message complete cb to
  // mirror h1x codec's behavior. when the stream closes, we need to
  // send another callback to clean up the stream's resources.
  if (ingressWebsocketUpgrade_) {
    ingressWebsocketUpgrade_ = false;
    deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageComplete,
                             "onMessageComplete", curHeader_.stream, true);
  }

  if (pendingEndStreamHandling_ && expectedContinuationStream_ == 0) {
    pendingEndStreamHandling_ = false;
    deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageComplete,
                             "onMessageComplete", curHeader_.stream, false);
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadHeaderPriority) {
  HTTPMessage req = getGetRequest();
  req.setHTTP2Priority(HTTPMessage::HTTPPriority(0, false, 7));
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  // hack ingress with cirular dep
  EXPECT_TRUE(parse([&] (IOBuf* ingress) {
        folly::io::RWPrivateCursor c(ingress);
        c.skip(http2::kFrameHeaderSize + http2::kConnectionPreface.length());
        c.writeBE<uint32_t>(1);
      }));

  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

void HTTP2Codec::streamError(const std::string& msg, ErrorCode code,
                             bool newTxn) {
  HTTPException error(HTTPException::Direction::INGRESS_AND_EGRESS,
                      msg);
  error.setCodecStatusCode(code);
  if (callback_) {
    callback_->onError(curHeader_.stream, error, newTxn);
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, GoawayHandling) {
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::ENABLE_PUSH, 1);
  upstreamCodec_.generateSettings(output_);

  // send request
  HTTPMessage req = getGetRequest();
  HTTPHeaderSize size;
  size.uncompressed = size.compressed = 0;
  upstreamCodec_.generateHeader(output_, 1, req, true, &size);
  EXPECT_GT(size.uncompressed, 0);
  parse();
  callbacks_.expectMessage(true, 1, "/");
  callbacks_.reset();

  SetUpUpstreamTest();
  // drain after this message
  downstreamCodec_.generateGoaway(output_, 1, ErrorCode::NO_ERROR);
  parseUpstream();
  // upstream cannot generate id > 1
  upstreamCodec_.generateHeader(output_, 3, req, false, &size);
  EXPECT_EQ(size.uncompressed, 0);
  upstreamCodec_.generateWindowUpdate(output_, 3, 100);
  upstreamCodec_.generateBody(output_, 3, makeBuf(10), HTTPCodec::NoPadding,
                              false);
  upstreamCodec_.generatePriority(output_, 3,
                                  HTTPMessage::HTTPPriority(0, true, 1));
  upstreamCodec_.generateEOM(output_, 3);
  upstreamCodec_.generateRstStream(output_, 3, ErrorCode::CANCEL);
  EXPECT_EQ(output_.chainLength(), 0);

  // send a push promise that will be rejected by downstream
  req.getHeaders().add("foomonkey", "george");
  downstreamCodec_.generatePushPromise(output_, 2, req, 1, false, &size);
  EXPECT_GT(size.uncompressed, 0);
  HTTPMessage resp;
  resp.setStatusCode(200);
  // send a push response that will be ignored
  downstreamCodec_.generateHeader(output_, 2, resp, false, &size);
  // window update for push doesn't make any sense, but whatever
  downstreamCodec_.generateWindowUpdate(output_, 2, 100);
  downstreamCodec_.generateBody(output_, 2, makeBuf(10), HTTPCodec::NoPadding,
                                false);
  writeFrameHeaderManual(output_, 20, (uint8_t)http2::FrameType::DATA, 0, 2);
  output_.append(makeBuf(10));

  // tell the upstream no pushing, and parse the first batch
  IOBufQueue dummy;
  upstreamCodec_.generateGoaway(dummy, 0, ErrorCode::NO_ERROR);
  parseUpstream();

  output_.append(makeBuf(10));
  downstreamCodec_.generatePriority(output_, 2,
                                    HTTPMessage::HTTPPriority(0, true, 1));
  downstreamCodec_.generateEOM(output_, 2);
  downstreamCodec_.generateRstStream(output_, 2, ErrorCode::CANCEL);

  // send a response that will be accepted, headers should be ok
  downstreamCodec_.generateHeader(output_, 1, resp, true, &size);
  EXPECT_GT(size.uncompressed, 0);

  // parse the remainder
  parseUpstream();
  callbacks_.expectMessage(true, 1, 200);
}

Project: proxygen

TEST_F(HTTP2CodecTest, SettingsTableSizeEarlyShrink) {
  // Lower size to 2k
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 2048);
  upstreamCodec_.generateSettings(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  downstreamCodec_.generateSettingsAck(output_);
  // Parsing SETTINGS ack updates upstream decoder to 2k
  parseUpstream();

  callbacks_.reset();

  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  SetUpUpstreamTest();
  // downstream encoder will send TSU/2k
  downstreamCodec_.generateHeader(output_, 1, resp);

  // sets pending table size to 512, but doesn't update it yet
  settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::HEADER_TABLE_SIZE, 512);
  IOBufQueue tmp{IOBufQueue::cacheChainLength()};
  upstreamCodec_.generateSettings(tmp);

  // Previous code would barf here, since TSU/2k is a violation of the current
  // max=512
  parseUpstream();
  callbacks_.expectMessage(false, 2, 200);
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadPseudoHeaders) {
  static const std::string v1("POST");
  static const std::string v2("http");
  static const std::string n3("foo");
  static const std::string v3("bar");
  static const std::string v4("/");
  static const vector<proxygen::compress::Header> reqHeaders = {
    Header::makeHeaderForTest(headers::kMethod, v1),
    Header::makeHeaderForTest(headers::kScheme, v2),
    Header::makeHeaderForTest(n3, v3),
    Header::makeHeaderForTest(headers::kPath, v4),
  };

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  HTTPCodec::StreamID stream = 1;
  std::vector<proxygen::compress::Header> allHeaders = reqHeaders;
  auto encodedHeaders = headerCodec.encode(allHeaders);
  http2::writeHeaders(output_,
                      std::move(encodedHeaders),
                      stream,
                      folly::none,
                      http2::kNoPadding,
                      true,
                      true);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseGoaway(Cursor& cursor) {
  VLOG(4) << "parsing GOAWAY frame length=" << curHeader_.length;
  uint32_t lastGoodStream = 0;
  ErrorCode statusCode = ErrorCode::NO_ERROR;
  std::unique_ptr<IOBuf> debugData;

  auto err = http2::parseGoaway(cursor, curHeader_, lastGoodStream, statusCode,
                                debugData);
  if (statusCode != ErrorCode::NO_ERROR) {
    VLOG(2) << "Goaway error statusCode=" << getErrorCodeString(statusCode)
            << " lastStream=" << lastGoodStream
            << " user-agent=" << userAgent_ <<  " debugData=" <<
      ((debugData) ? string((char*)debugData->data(), debugData->length()):
       empty_string);
  }
  RETURN_IF_ERROR(err);
  if (lastGoodStream < ingressGoawayAck_) {
    ingressGoawayAck_ = lastGoodStream;
    // Drain all streams <= lastGoodStream
    // and abort streams > lastGoodStream
    if (callback_) {
      callback_->onGoaway(lastGoodStream, statusCode, std::move(debugData));
    }
  } else {
    LOG(WARNING) << "Received multiple GOAWAY with increasing ack";
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

void HTTP2Codec::generateHeaderImpl(
    folly::IOBufQueue& writeBuf,
    StreamID stream,
    const HTTPMessage& msg,
    const folly::Optional<StreamID>& assocStream,
    const folly::Optional<HTTPCodec::ExAttributes>& exAttributes,
    bool eom,
    HTTPHeaderSize* size) {
  if (assocStream) {
    CHECK(!exAttributes);
    VLOG(4) << "generating PUSH_PROMISE for stream=" << stream;
  } else if (exAttributes) {
    CHECK(!assocStream);
    VLOG(4) << "generating ExHEADERS for stream=" << stream
            << " with control stream=" << exAttributes->controlStream
            << " unidirectional=" << exAttributes->unidirectional;
  } else {
    VLOG(4) << "generating HEADERS for stream=" << stream;
  }

  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "Suppressing HEADERS/PROMISE for stream=" << stream <<
      " ingressGoawayAck_=" << ingressGoawayAck_;
    if (size) {
      size->uncompressed = 0;
      size->compressed = 0;
    }
    return;
  }

  if (msg.isRequest()) {
    DCHECK(transportDirection_ == TransportDirection::UPSTREAM ||
           assocStream || exAttributes);
    if (msg.isEgressWebsocketUpgrade()) {
      upgradedStreams_.insert(stream);
    }
  } else {
    DCHECK(transportDirection_ == TransportDirection::DOWNSTREAM ||
           exAttributes);
  }

  std::vector<std::string> temps;
  auto allHeaders = CodecUtil::prepareMessageForCompression(msg, temps);
  auto out = encodeHeaders(msg.getHeaders(), allHeaders, size);
  IOBufQueue queue(IOBufQueue::cacheChainLength());
  queue.append(std::move(out));
  auto maxFrameSize = maxSendFrameSize();
  if (queue.chainLength() > 0) {
    folly::Optional<http2::PriorityUpdate> pri;
    auto res = msg.getHTTP2Priority();
    auto remainingFrameSize = maxFrameSize;
    if (res) {
      pri = http2::PriorityUpdate{std::get<0>(*res), std::get<1>(*res),
                                  std::get<2>(*res)};
      DCHECK_GE(remainingFrameSize, http2::kFramePrioritySize)
        << "no enough space for priority? frameHeadroom=" << remainingFrameSize;
      remainingFrameSize -= http2::kFramePrioritySize;
    }
    auto chunk = queue.split(std::min(remainingFrameSize, queue.chainLength()));

    bool endHeaders = queue.chainLength() == 0;

    if (assocStream) {
      DCHECK_EQ(transportDirection_, TransportDirection::DOWNSTREAM);
      DCHECK(!eom);
      generateHeaderCallbackWrapper(stream, http2::FrameType::PUSH_PROMISE,
                                    http2::writePushPromise(writeBuf,
                                                            *assocStream,
                                                            stream,
                                                            std::move(chunk),
                                                            http2::kNoPadding,
                                                            endHeaders));
    } else if (exAttributes) {
      generateHeaderCallbackWrapper(
        stream,
        http2::FrameType::EX_HEADERS,
        http2::writeExHeaders(writeBuf,
                              std::move(chunk),
                              stream,
                              *exAttributes,
                              pri,
                              http2::kNoPadding,
                              eom,
                              endHeaders));
    } else {
      generateHeaderCallbackWrapper(stream, http2::FrameType::HEADERS,
                                    http2::writeHeaders(writeBuf,
                                                        std::move(chunk),
                                                        stream,
                                                        pri,
                                                        http2::kNoPadding,
                                                        eom,
                                                        endHeaders));
    }

    if (!endHeaders) {
      generateContinuation(writeBuf, queue, stream, maxFrameSize);
    }
  }
}

Project: proxygen

ErrorCode HTTP2Codec::parsePriority(Cursor& cursor) {
  VLOG(4) << "parsing PRIORITY frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  http2::PriorityUpdate pri;
  auto err = http2::parsePriority(cursor, curHeader_, pri);
  RETURN_IF_ERROR(err);
  if (curHeader_.stream == pri.streamDependency) {
    streamError(folly::to<string>("Circular dependency for txn=",
                                  curHeader_.stream),
                ErrorCode::PROTOCOL_ERROR, false);
    return ErrorCode::NO_ERROR;
  }
  deliverCallbackIfAllowed(&HTTPCodec::Callback::onPriority, "onPriority",
                           curHeader_.stream,
                           std::make_tuple(pri.streamDependency,
                                           pri.exclusive,
                                           pri.weight));
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, WebsocketDupProtocol) {
  const std::string kConnect{"CONNECT"};
  const std::string kWebsocketPath{"/websocket"};
  const std::string kSchemeHttps{"https"};
  vector<proxygen::compress::Header> headers = {
    Header::makeHeaderForTest(headers::kMethod, kConnect),
    Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),
    Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),
    Header::makeHeaderForTest(headers::kPath, kWebsocketPath),
    Header::makeHeaderForTest(headers::kScheme, kSchemeHttps),
  };
  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  auto encodedHeaders = headerCodec.encode(headers);
  http2::writeHeaders(output_,
                      std::move(encodedHeaders),
                      1,
                      folly::none,
                      http2::kNoPadding,
                      false,
                      true);
  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

size_t HTTP2Codec::onIngress(const folly::IOBuf& buf) {
  // TODO: ensure only 1 parse at a time on stack.
  FOLLY_SCOPED_TRACE_SECTION("HTTP2Codec - onIngress");

  Cursor cursor(&buf);
  size_t parsed = 0;
  ErrorCode connError = ErrorCode::NO_ERROR;
  for (auto bufLen = cursor.totalLength();
       connError == ErrorCode::NO_ERROR;
       bufLen = cursor.totalLength()) {
    if (frameState_ == FrameState::UPSTREAM_CONNECTION_PREFACE) {
      if (bufLen >= http2::kConnectionPreface.length()) {
        auto test = cursor.readFixedString(http2::kConnectionPreface.length());
        parsed += http2::kConnectionPreface.length();
        if (test != http2::kConnectionPreface) {
          goawayErrorMessage_ = "missing connection preface";
          VLOG(4) << goawayErrorMessage_;
          connError = ErrorCode::PROTOCOL_ERROR;
        }
        frameState_ = FrameState::FRAME_HEADER;
      } else {
        break;
      }
    } else if (frameState_ == FrameState::FRAME_HEADER ||
               frameState_ == FrameState::DOWNSTREAM_CONNECTION_PREFACE) {
      // Waiting to parse the common frame header
      if (bufLen >= http2::kFrameHeaderSize) {
        connError = parseFrameHeader(cursor, curHeader_);
        parsed += http2::kFrameHeaderSize;
        if (frameState_ == FrameState::DOWNSTREAM_CONNECTION_PREFACE &&
            curHeader_.type != http2::FrameType::SETTINGS) {
          goawayErrorMessage_ = folly::to<string>(
              "GOAWAY error: got invalid connection preface frame type=",
              getFrameTypeString(curHeader_.type), "(", curHeader_.type, ")",
              " for streamID=", curHeader_.stream);
          VLOG(4) << goawayErrorMessage_;
          connError = ErrorCode::PROTOCOL_ERROR;
        }
        if (curHeader_.length > maxRecvFrameSize()) {
          VLOG(4) << "Excessively large frame len=" << curHeader_.length;
          connError = ErrorCode::FRAME_SIZE_ERROR;
        }

        if (callback_) {
          callback_->onFrameHeader(
            curHeader_.stream,
            curHeader_.flags,
            curHeader_.length,
            static_cast<uint8_t>(curHeader_.type));
        }

        frameState_ = (curHeader_.type == http2::FrameType::DATA) ?
          FrameState::DATA_FRAME_DATA : FrameState::FRAME_DATA;
        pendingDataFrameBytes_ = curHeader_.length;
        pendingDataFramePaddingBytes_ = 0;
#ifndef NDEBUG
        receivedFrameCount_++;
#endif
      } else {
        break;
      }
    } else if (frameState_ == FrameState::DATA_FRAME_DATA && bufLen > 0 &&
               (bufLen < curHeader_.length ||
                pendingDataFrameBytes_ < curHeader_.length)) {
      // FrameState::DATA_FRAME_DATA with partial data only
      size_t dataParsed = 0;
      connError = parseDataFrameData(cursor, bufLen, dataParsed);
      if (dataParsed == 0 && pendingDataFrameBytes_ > 0) {
        // We received only the padding byte, we will wait for more
        break;
      } else {
        parsed += dataParsed;
        if (pendingDataFrameBytes_ == 0) {
          frameState_ = FrameState::FRAME_HEADER;
        }
      }
    } else { // FrameState::FRAME_DATA
             // or FrameState::DATA_FRAME_DATA with all data available
      // Already parsed the common frame header
      const auto frameLen = curHeader_.length;
      if (bufLen >= frameLen) {
        connError = parseFrame(cursor);
        parsed += curHeader_.length;
        frameState_ = FrameState::FRAME_HEADER;
      } else {
        break;
      }
    }
  }
  checkConnectionError(connError, &buf);
  return parsed;
}

Project: proxygen

bool HTTP2Codec::checkConnectionError(ErrorCode err, const folly::IOBuf* buf) {
  if (err != ErrorCode::NO_ERROR) {
    LOG(ERROR) << "Connection error " << getErrorCodeString(err)
               << " with ingress=";
    VLOG(3) << IOBufPrinter::printHexFolly(buf, true);
    if (callback_) {
      std::string errorDescription = goawayErrorMessage_.empty() ?
        "Connection error" : goawayErrorMessage_;
      HTTPException ex(HTTPException::Direction::INGRESS_AND_EGRESS,
                       errorDescription);
      ex.setCodecStatusCode(err);
      callback_->onError(0, ex, false);
    }
    return true;
  }
  return false;
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersReplyContinuation) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  downstreamCodec_.generateHeader(output_, 1, resp);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  trailers.add("x-huge-trailer",
               std::string(http2::kMaxFramePayloadLengthMin, '!'));
  downstreamCodec_.generateTrailers(output_, 1, trailers);

  parseUpstream();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_NE(callbacks_.msg, nullptr);
  EXPECT_EQ(callbacks_.msg->getStatusCode(), 200);
  EXPECT_EQ(1, callbacks_.trailers);
  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
  EXPECT_EQ(std::string(http2::kMaxFramePayloadLengthMin, '!'),
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-huge-trailer"));
#ifndef NDEBUG
  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);
#endif
}

Project: proxygen

size_t HTTP2Codec::generateSettingsAck(folly::IOBufQueue& writeBuf) {
  VLOG(4) << getTransportDirectionString(getTransportDirection())
          << " generating settings ack";
  return generateHeaderCallbackWrapper(0, http2::FrameType::SETTINGS,
                                       http2::writeSettingsAck(writeBuf));
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicCertificate) {
  uint16_t certId = 17;
  std::unique_ptr<folly::IOBuf> authenticator =
      folly::IOBuf::copyBuffer("authenticatorData");
  upstreamCodec_.generateCertificate(output_, certId, std::move(authenticator));

  parse();
  EXPECT_EQ(callbacks_.certificates, 1);
  EXPECT_EQ(callbacks_.lastCertId, certId);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), "authenticatorData");
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

bool HTTP2Codec::parsingTrailers() const {
  // HEADERS frame is used for request/response headers and trailers.
  // Per spec, specific role of HEADERS frame is determined by it's postion
  // within the stream. We don't keep full stream state in this codec,
  // thus using heuristics to distinguish between headers/trailers.
  // For DOWNSTREAM case, request headers HEADERS frame would be creating
  // new stream, thus HEADERS on existing stream ID are considered trailers
  // (see checkNewStream).
  // For UPSTREAM case, response headers are required to have status code,
  // thus if no status code we consider that trailers.
  if (curHeader_.type == http2::FrameType::HEADERS ||
      curHeader_.type == http2::FrameType::CONTINUATION) {
    if (transportDirection_ == TransportDirection::DOWNSTREAM) {
      return parsingDownstreamTrailers_;
    } else {
      return !decodeInfo_.hasStatus();
    }
  }
  return false;
}

Project: proxygen

std::unique_ptr<folly::IOBuf> HTTP2Codec::encodeHeaders(
    const HTTPHeaders& headers,
    std::vector<compress::Header>& allHeaders,
    HTTPHeaderSize* size) {
  headerCodec_.setEncodeHeadroom(http2::kFrameHeaderSize +
                                 http2::kFrameHeadersBaseMaxSize);
  auto out = headerCodec_.encode(allHeaders);
  if (size) {
    *size = headerCodec_.getEncodedSize();
  }

  if (headerCodec_.getEncodedSize().uncompressed >
      ingressSettings_.getSetting(SettingsId::MAX_HEADER_LIST_SIZE,
                                  std::numeric_limits<uint32_t>::max())) {
    // The remote side told us they don't want headers this large...
    // but this function has no mechanism to fail
    string serializedHeaders;
    headers.forEach(
      [&serializedHeaders] (const string& name, const string& value) {
        serializedHeaders = folly::to<string>(serializedHeaders, "\\n", name,
                                              ":", value);
      });
    LOG(ERROR) << "generating HEADERS frame larger than peer maximum nHeaders="
               << headers.size() << " all headers="
               << serializedHeaders;
  }
  return out;
}

Project: proxygen

TEST_F(HTTP2CodecTest, ZeroWindow) {
  auto streamID = HTTPCodec::StreamID(1);
  // First generate a frame with delta=1 so as to pass the checks, and then
  // hack the frame so that delta=0 without modifying other checks
  upstreamCodec_.generateWindowUpdate(output_, streamID, 1);
  output_.trimEnd(http2::kFrameWindowUpdateSize);
  QueueAppender appender(&output_, http2::kFrameWindowUpdateSize);
  appender.writeBE<uint32_t>(0);

  parse();
  // This test doesn't ensure that RST_STREAM is generated
  EXPECT_EQ(callbacks_.windowUpdateCalls, 0);
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),
      ErrorCode::PROTOCOL_ERROR);
}

Project: proxygen

void HTTP2CodecTest::testHeaderListSize(bool oversized) {
  if (oversized) {
    auto settings = downstreamCodec_.getEgressSettings();
    settings->setSetting(SettingsId::MAX_HEADER_LIST_SIZE, 37);
  }

  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  req.getHeaders().add("x-long-long-header",
                       "supercalafragalisticexpialadoshus");
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  parse();
  // session error
  EXPECT_EQ(callbacks_.messageBegin, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.headersComplete, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.messageComplete, oversized ? 0 : 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, oversized ? 1 : 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseHeadersImpl(
    Cursor& /*cursor*/,
    std::unique_ptr<IOBuf> headerBuf,
    const folly::Optional<http2::PriorityUpdate>& priority,
    const folly::Optional<uint32_t>& promisedStream,
    const folly::Optional<ExAttributes>& exAttributes) {
  curHeaderBlock_.append(std::move(headerBuf));
  std::unique_ptr<HTTPMessage> msg;
  if (curHeader_.flags & http2::END_HEADERS) {
    auto errorCode =
        parseHeadersDecodeFrames(priority, promisedStream, exAttributes, msg);
    if (errorCode.hasValue()) {
      return errorCode.value();
    }
  }

  // if we're not parsing CONTINUATION, then it's start of new header block
  if (curHeader_.type != http2::FrameType::CONTINUATION) {
    headerBlockFrameType_ = curHeader_.type;
  }

  // Report back what we've parsed
  if (callback_) {
    auto concurError = parseHeadersCheckConcurrentStreams(priority);
    if (concurError.hasValue()) {
      return concurError.value();
    }
    uint32_t headersCompleteStream = curHeader_.stream;
    bool trailers = parsingTrailers();
    bool allHeaderFramesReceived =
        (curHeader_.flags & http2::END_HEADERS) &&
        (headerBlockFrameType_ == http2::FrameType::HEADERS);
    if (allHeaderFramesReceived && !trailers) {
      // Only deliver onMessageBegin once per stream.
      // For responses with CONTINUATION, this will be delayed until
      // the frame with the END_HEADERS flag set.
      if (!deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageBegin,
                                    "onMessageBegin",
                                    curHeader_.stream,
                                    msg.get())) {
        return handleEndStream();
      }
   } else if (curHeader_.type == http2::FrameType::EX_HEADERS) {
      if (!deliverCallbackIfAllowed(&HTTPCodec::Callback::onExMessageBegin,
                                    "onExMessageBegin",
                                    curHeader_.stream,
                                    exAttributes->controlStream,
                                    exAttributes->unidirectional,
                                    msg.get())) {
        return handleEndStream();
      }
    } else if (curHeader_.type == http2::FrameType::PUSH_PROMISE) {
      DCHECK(promisedStream);
      if (!deliverCallbackIfAllowed(&HTTPCodec::Callback::onPushMessageBegin,
                                    "onPushMessageBegin", *promisedStream,
                                    curHeader_.stream, msg.get())) {
        return handleEndStream();
      }
      headersCompleteStream = *promisedStream;
    }

    if (curHeader_.flags & http2::END_HEADERS && msg) {
      if (!(curHeader_.flags & http2::END_STREAM)) {
        // If it there are DATA frames coming, consider it chunked
        msg->setIsChunked(true);
      }
      if (trailers) {
        VLOG(4) << "Trailers complete for streamId=" << headersCompleteStream
                << " direction=" << transportDirection_;
        auto trailerHeaders =
            std::make_unique<HTTPHeaders>(msg->extractHeaders());
        msg.reset();
        callback_->onTrailersComplete(headersCompleteStream,
                                      std::move(trailerHeaders));
      } else {
        callback_->onHeadersComplete(headersCompleteStream, std::move(msg));
      }
    }
    return handleEndStream();
  }
  return ErrorCode::NO_ERROR;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicContinuationEndStream) {
  // CONTINUATION with END_STREAM flag set on the preceding HEADERS frame
  HTTPMessage req = getBigGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  parse();
  callbacks_.expectMessage(true, -1, "/");
#ifndef NDEBUG
  EXPECT_GT(downstreamCodec_.getReceivedFrameCount(), 1);
#endif
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ("coolio", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));
  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseHeaders(Cursor& cursor) {
  FOLLY_SCOPED_TRACE_SECTION("HTTP2Codec - parseHeaders");
  folly::Optional<http2::PriorityUpdate> priority;
  std::unique_ptr<IOBuf> headerBuf;
  VLOG(4) << "parsing HEADERS frame for stream=" << curHeader_.stream <<
    " length=" << curHeader_.length;
  auto err = http2::parseHeaders(cursor, curHeader_, priority, headerBuf);
  RETURN_IF_ERROR(err);
  if (transportDirection_ == TransportDirection::DOWNSTREAM) {
    RETURN_IF_ERROR(
        checkNewStream(curHeader_.stream, true /* trailersAllowed */));
  }
  err = parseHeadersImpl(cursor, std::move(headerBuf), priority, folly::none,
                         folly::none);
  return err;
}

Project: proxygen

TEST_F(HTTP2CodecTest, MissingContinuation) {
  IOBufQueue output(IOBufQueue::cacheChainLength());
  HTTPMessage req = getBigGetRequest();

  upstreamCodec_.generateHeader(output_, 1, req);
  // empirically determined the size of continuation frame, and strip it
  output_.trimEnd(http2::kFrameHeaderSize + 4134);

  // insert a non-continuation (but otherwise valid) frame
  http2::writeGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 2);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicData) {
  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  upstreamCodec_.generateBody(output_, 2, std::move(buf),
                              HTTPCodec::NoPadding, true);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, 5);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), data);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicCertificateRequest) {
  uint16_t requestId = 17;
  std::unique_ptr<folly::IOBuf> authRequest =
      folly::IOBuf::copyBuffer("authRequestData");
  upstreamCodec_.generateCertificateRequest(
      output_, requestId, std::move(authRequest));

  parse();
  EXPECT_EQ(callbacks_.certificateRequests, 1);
  EXPECT_EQ(callbacks_.lastCertRequestId, requestId);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), "authRequestData");
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, FrameTooLarge) {
  writeFrameHeaderManual(output_, 1 << 15, 0, 0, 1);

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
  EXPECT_TRUE(callbacks_.lastParseError->hasCodecStatusCode());
  EXPECT_EQ(callbacks_.lastParseError->getCodecStatusCode(),
            ErrorCode::FRAME_SIZE_ERROR);
}

Project: proxygen

TEST_F(HTTP2CodecTest, Normal1024Continuation) {
  HTTPMessage req = getGetRequest();
  string bigval(8691, '!');
  bigval.append(8691, ' ');
  req.getHeaders().add("x-headr", bigval);
  req.setHTTP2Priority(HTTPMessage::HTTPPriority(0, false, 7));
  upstreamCodec_.generateHeader(output_, 1, req);

  parse();
  callbacks_.expectMessage(false, -1, "/");
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ(bigval, headers.getSingleOrEmpty("x-headr"));
  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);

  upstreamCodec_.generateSettingsAck(output_);
  parse();
  EXPECT_EQ(callbacks_.settingsAcks, 1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, DoubleGoawayWithError) {
  SetUpUpstreamTest();
  std::unique_ptr<folly::IOBuf> debugData =
    folly::IOBuf::copyBuffer("debugData");
  downstreamCodec_.generateGoaway(output_, std::numeric_limits<int32_t>::max(),
                                  ErrorCode::ENHANCE_YOUR_CALM,
                                  std::move(debugData));
  EXPECT_FALSE(downstreamCodec_.isWaitingToDrain());
  EXPECT_FALSE(downstreamCodec_.isReusable());
  auto ret = downstreamCodec_.generateGoaway(output_, 0,
                                             ErrorCode::NO_ERROR);
  EXPECT_EQ(ret, 0);

  parseUpstream();
  EXPECT_EQ(callbacks_.goaways, 1);
  EXPECT_EQ(callbacks_.data.move()->moveToFbString(), "debugData");
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseDataFrameData(Cursor& cursor,
                                         size_t bufLen,
                                         size_t& parsed) {
  FOLLY_SCOPED_TRACE_SECTION("HTTP2Codec - parseDataFrameData");
  if (bufLen == 0) {
    VLOG(10) << "No data to parse";
    return ErrorCode::NO_ERROR;
  }

  std::unique_ptr<IOBuf> outData;
  uint16_t padding = 0;
  VLOG(10) << "parsing DATA frame data for stream=" << curHeader_.stream <<
    " frame data length=" << curHeader_.length << " pendingDataFrameBytes_=" <<
    pendingDataFrameBytes_ << " pendingDataFramePaddingBytes_=" <<
    pendingDataFramePaddingBytes_ << " bufLen=" << bufLen <<
    " parsed=" << parsed;
  // Parse the padding information only the first time
  if (pendingDataFrameBytes_ == curHeader_.length &&
    pendingDataFramePaddingBytes_ == 0) {
    if (frameHasPadding(curHeader_) && bufLen == 1) {
      // We need to wait for more bytes otherwise we won't be able to pass
      // the correct padding to the first onBody call
      return ErrorCode::NO_ERROR;
    }
    const auto ret = http2::parseDataBegin(cursor, curHeader_, parsed, padding);
    RETURN_IF_ERROR(ret);
    if (padding > 0) {
      pendingDataFramePaddingBytes_ = padding - 1;
      pendingDataFrameBytes_--;
      bufLen--;
      parsed++;
    }
    VLOG(10) << "out padding=" << padding << " pendingDataFrameBytes_=" <<
      pendingDataFrameBytes_ << " pendingDataFramePaddingBytes_=" <<
      pendingDataFramePaddingBytes_ << " bufLen=" << bufLen <<
      " parsed=" << parsed;
  }
  if (bufLen > 0) {
    // Check if we have application data to parse
    if (pendingDataFrameBytes_ > pendingDataFramePaddingBytes_) {
      const size_t pendingAppData =
        pendingDataFrameBytes_ - pendingDataFramePaddingBytes_;
      const size_t toClone = std::min(pendingAppData, bufLen);
      cursor.clone(outData, toClone);
      bufLen -= toClone;
      pendingDataFrameBytes_ -= toClone;
      parsed += toClone;
      VLOG(10) << "parsed some app data, pendingDataFrameBytes_=" <<
        pendingDataFrameBytes_ << " pendingDataFramePaddingBytes_=" <<
        pendingDataFramePaddingBytes_ << " bufLen=" << bufLen <<
        " parsed=" << parsed;
    }
    // Check if we have padding bytes to parse
    if (bufLen > 0 && pendingDataFramePaddingBytes_ > 0) {
      size_t toSkip = 0;
      auto ret = http2::parseDataEnd(cursor, bufLen,
                                     pendingDataFramePaddingBytes_, toSkip);
      RETURN_IF_ERROR(ret);
      pendingDataFrameBytes_ -= toSkip;
      pendingDataFramePaddingBytes_ -= toSkip;
      parsed += toSkip;
      VLOG(10) << "parsed some padding, pendingDataFrameBytes_=" <<
        pendingDataFrameBytes_ << " pendingDataFramePaddingBytes_=" <<
        pendingDataFramePaddingBytes_ << " bufLen=" << bufLen <<
        " parsed=" << parsed;
    }
  }

  if (callback_ && (padding > 0 || (outData && !outData->empty()))) {
    if (!outData) {
      outData = std::make_unique<IOBuf>();
    }
    deliverCallbackIfAllowed(&HTTPCodec::Callback::onBody, "onBody",
                             curHeader_.stream, std::move(outData), padding);
  }
  return (pendingDataFrameBytes_ > 0) ? ErrorCode::NO_ERROR : handleEndStream();
}

Project: proxygen

TEST_F(HTTP2CodecTest, HTTP2EnableConnect) {
  SetUpUpstreamTest();
  // egress settings have no connect settings.
  auto ws_enable = upstreamCodec_.getEgressSettings()->getSetting(
      SettingsId::ENABLE_CONNECT_PROTOCOL);
  // enable connect settings, and check.
  upstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_CONNECT_PROTOCOL, 1);
  ws_enable = upstreamCodec_.getEgressSettings()->getSetting(
      SettingsId::ENABLE_CONNECT_PROTOCOL);
  EXPECT_EQ(ws_enable->value, 1);
  // generateSettings.
  // pass the buffer to be parsed by the codec and check for ingress settings.
  upstreamCodec_.generateSettings(output_);
  parseUpstream();
  EXPECT_EQ(1, upstreamCodec_.peerHasWebsockets());
}

Project: proxygen

TEST_F(HTTP2CodecTest, MissingContinuationBadFrame) {
  IOBufQueue output(IOBufQueue::cacheChainLength());
  HTTPMessage req = getBigGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req);

  // empirically determined the size of continuation frame, and fake it
  output_.trimEnd(http2::kFrameHeaderSize + 4134);

  // insert an invalid frame
  auto frame = makeBuf(http2::kFrameHeaderSize + 4134);
  *((uint32_t *)frame->writableData()) = 0xfa000000;
  output_.append(std::move(frame));

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
#ifndef NDEBUG
  EXPECT_EQ(downstreamCodec_.getReceivedFrameCount(), 2);
#endif
}

Project: proxygen

size_t HTTP2Codec::addPriorityNodes(
    PriorityQueue& queue,
    folly::IOBufQueue& writeBuf,
    uint8_t maxLevel) {
  HTTPCodec::StreamID parent = 0;
  size_t bytes = 0;
  while (maxLevel--) {
    auto id = createStream();
    virtualPriorityNodes_.push_back(id);
    queue.addPriorityNode(id, parent);
    bytes += generatePriority(writeBuf, id, std::make_tuple(parent, false, 0));
    parent = id;
  }
  return bytes;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadServerPreface) {
  output_.move();
  downstreamCodec_.generateWindowUpdate(output_, 0, 10);
  parseUpstream();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.assocStreamId, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

void HTTP2Codec::generateExHeader(folly::IOBufQueue& writeBuf,
                                  StreamID stream,
                                  const HTTPMessage& msg,
                                  const HTTPCodec::ExAttributes& exAttributes,
                                  bool eom,
                                  HTTPHeaderSize* size) {
  generateHeaderImpl(writeBuf,
                     stream,
                     msg,
                     folly::none, /* assocStream */
                     exAttributes,
                     eom,
                     size);
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersReplyMissingContinuation) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  downstreamCodec_.generateHeader(output_, 1, resp);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  trailers.add("x-huge-trailer",
               std::string(http2::kMaxFramePayloadLengthMin, '!'));
  downstreamCodec_.generateTrailers(output_, 1, trailers);
  // empirically determined the size of continuation frame, and strip it
  output_.trimEnd(http2::kFrameHeaderSize + 4132);

  // insert a non-continuation (but otherwise valid) frame
  http2::writeGoaway(output_, 17, ErrorCode::ENHANCE_YOUR_CALM);

  parseUpstream();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
#ifndef NDEBUG
  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);
#endif
}

Project: proxygen

size_t HTTP2Codec::generateHeaderCallbackWrapper(StreamID stream,
                                                 http2::FrameType type,
                                                 size_t length) {
  if (callback_) {
    callback_->onGenerateFrameHeader(stream,
                                     static_cast<uint8_t>(type),
                                     length);
  }
  return length;
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicPing) {
  upstreamCodec_.generatePingRequest(output_);
  upstreamCodec_.generatePingReply(output_, 17);

  uint64_t pingReq;
  parse([&] (IOBuf* ingress) {
      folly::io::Cursor c(ingress);
      c.skip(http2::kFrameHeaderSize + http2::kConnectionPreface.length());
      pingReq = c.read<uint64_t>();
    });

  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.bodyCalls, 0);
  EXPECT_EQ(callbacks_.recvPingRequest, pingReq);
  EXPECT_EQ(callbacks_.recvPingReply, 17);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersReply) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  downstreamCodec_.generateHeader(output_, 1, resp);

  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  downstreamCodec_.generateBody(
      output_, 1, std::move(buf), HTTPCodec::NoPadding, false);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  trailers.add("x-trailer-2", "chicken-kyiv");
  downstreamCodec_.generateTrailers(output_, 1, trailers);

  parseUpstream();

  callbacks_.expectMessage(true, 2, 200);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, 5);
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
  EXPECT_EQ(1, callbacks_.trailers);
  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
  EXPECT_EQ("chicken-kyiv",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-2"));
#ifndef NDEBUG
  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 4);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicHeaderReply) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  downstreamCodec_.generateHeader(output_, 1, resp);
  downstreamCodec_.generateEOM(output_, 1);

  parseUpstream();
  callbacks_.expectMessage(true, 2, 200);
  const auto& headers = callbacks_.msg->getHeaders();
  // HTTP/2 doesnt support serialization - instead you get the default
  EXPECT_EQ("OK", callbacks_.msg->getStatusMessage());
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersReplyWithNoData) {
  SetUpUpstreamTest();
  HTTPMessage resp;
  resp.setStatusCode(200);
  resp.setStatusMessage("nifty-nice");
  resp.getHeaders().add(HTTP_HEADER_CONTENT_TYPE, "x-coolio");
  downstreamCodec_.generateHeader(output_, 1, resp);

  HTTPHeaders trailers;
  trailers.add("x-trailer-1", "pico-de-gallo");
  downstreamCodec_.generateTrailers(output_, 1, trailers);

  parseUpstream();

  callbacks_.expectMessage(true, 2, 200);
  EXPECT_EQ(callbacks_.bodyCalls, 0);
  EXPECT_EQ(callbacks_.bodyLength, 0);
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_TRUE(callbacks_.msg->getHeaders().exists(HTTP_HEADER_DATE));
  EXPECT_EQ("x-coolio", headers.getSingleOrEmpty(HTTP_HEADER_CONTENT_TYPE));
  EXPECT_EQ(1, callbacks_.trailers);
  EXPECT_NE(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ("pico-de-gallo",
            callbacks_.msg->getTrailers()->getSingleOrEmpty("x-trailer-1"));
#ifndef NDEBUG
  EXPECT_EQ(upstreamCodec_.getReceivedFrameCount(), 3);
#endif
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadPriority) {
  auto pri = HTTPMessage::HTTPPriority(0, true, 1);
  upstreamCodec_.generatePriority(output_, 1, pri);

  // hack ingress with cirular dep
  EXPECT_TRUE(parse([&] (IOBuf* ingress) {
        folly::io::RWPrivateCursor c(ingress);
        c.skip(http2::kFrameHeaderSize + http2::kConnectionPreface.length());
        c.writeBE<uint32_t>(1);
      }));

  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

ErrorCode HTTP2Codec::parseExHeaders(Cursor& cursor) {
  FOLLY_SCOPED_TRACE_SECTION("HTTP2Codec - parseExHeaders");
  HTTPCodec::ExAttributes exAttributes;
  folly::Optional<http2::PriorityUpdate> priority;
  std::unique_ptr<IOBuf> headerBuf;
  VLOG(4) << "parsing ExHEADERS frame for stream=" << curHeader_.stream
          << " length=" << curHeader_.length;
  auto err = http2::parseExHeaders(
      cursor, curHeader_, exAttributes, priority, headerBuf);
  RETURN_IF_ERROR(err);
  if (isRequest(curHeader_.stream)) {
    RETURN_IF_ERROR(
        checkNewStream(curHeader_.stream, false /* trailersAllowed */));
  }
  return parseHeadersImpl(cursor, std::move(headerBuf), priority, folly::none,
                          exAttributes);
}

Project: proxygen

size_t HTTP2Codec::generateGoaway(folly::IOBufQueue& writeBuf,
                                  StreamID lastStream,
                                  ErrorCode statusCode,
                                  std::unique_ptr<folly::IOBuf> debugData) {
  DCHECK_LE(lastStream, egressGoawayAck_) << "Cannot increase last good stream";
  egressGoawayAck_ = lastStream;
  if (sessionClosing_ == ClosingState::CLOSED) {
    VLOG(4) << "Not sending GOAWAY for closed session";
    return 0;
  }
  switch (sessionClosing_) {
    case ClosingState::OPEN:
    case ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED:
      if (lastStream == std::numeric_limits<int32_t>::max() &&
          statusCode == ErrorCode::NO_ERROR) {
        sessionClosing_ = ClosingState::FIRST_GOAWAY_SENT;
      } else {
        // The user of this codec decided not to do the double goaway
        // drain, or this is not a graceful shutdown
        sessionClosing_ = ClosingState::CLOSED;
      }
      break;
    case ClosingState::FIRST_GOAWAY_SENT:
      sessionClosing_ = ClosingState::CLOSED;
      break;
    case ClosingState::CLOSING:
    case ClosingState::CLOSED:
      LOG(FATAL) << "unreachable";
  }

  VLOG(4) << "Sending GOAWAY with last acknowledged stream="
          << lastStream << " with code=" << getErrorCodeString(statusCode);
  if (statusCode == ErrorCode::PROTOCOL_ERROR) {
    VLOG(2) << "sending GOAWAY with last acknowledged stream=" << lastStream
            << " with code=" << getErrorCodeString(statusCode)
            << " user-agent=" << userAgent_;
  }

  auto code = http2::errorCodeToGoaway(statusCode);
  return generateHeaderCallbackWrapper(
            0,
            http2::FrameType::GOAWAY,
            http2::writeGoaway(writeBuf,
                              lastStream,
                              code,
                              std::move(debugData)));
}

Project: proxygen

TEST_F(HTTP2CodecTest, TrailersWithPseudoHeaders) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  upstreamCodec_.generateHeader(output_, 1, req);

  string data("abcde");
  auto buf = folly::IOBuf::copyBuffer(data.data(), data.length());
  upstreamCodec_.generateBody(
      output_, 1, std::move(buf), HTTPCodec::NoPadding, false /* eom */);

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  std::string post("POST");
  std::vector<proxygen::compress::Header> trailers = {
      Header::makeHeaderForTest(headers::kMethod, post)};
  auto encodedTrailers = headerCodec.encode(trailers);
  http2::writeHeaders(output_,
                      std::move(encodedTrailers),
                      1,
                      folly::none,
                      http2::kNoPadding,
                      true,
                      true);

  parse();

  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.bodyCalls, 1);
  EXPECT_EQ(callbacks_.bodyLength, 5);
  EXPECT_EQ(callbacks_.trailers, 0);
  EXPECT_EQ(nullptr, callbacks_.msg->getTrailers());
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, MalformedPaddingLength) {
  const uint8_t badInput[] = {0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
                              0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
                              0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a,
                              0x00, 0x00, 0x7e, 0x00, 0x6f, 0x6f, 0x6f, 0x6f,
                              // The padding length byte below is 0x82 (130
                              // in decimal) which is greater than the length
                              // specified by the header's length field, 126
                              0x01, 0x82, 0x87, 0x44, 0x87, 0x92, 0x97, 0x92,
                              0x92, 0x92, 0x7a, 0x0b, 0x41, 0x89, 0xf1, 0xe3,
                              0xc0, 0xf2, 0x9c, 0xdd, 0x90, 0xf4, 0xff, 0x40,
                              0x80, 0x84, 0x2d, 0x35, 0xa7, 0xd7};
  output_.clear();
  output_.append(badInput, sizeof(badInput));
  EXPECT_EQ(output_.chainLength(), sizeof(badInput));

  EXPECT_FALSE(parse());
}

Project: proxygen

TEST_F(HTTP2CodecTest, HTTP2SettingsFailure) {
  HTTPMessage req = getGetRequest("/guacamole");
  // no settings
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));

  HTTPHeaders& headers = req.getHeaders();

  // Not base64_url settings
  headers.set(http2::kProtocolSettingsHeader, "????");
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));
  headers.set(http2::kProtocolSettingsHeader, "AAA");
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));

  // Too big
  string bigSettings((http2::kMaxFramePayloadLength + 1) * 4 / 3, 'A');
  headers.set(http2::kProtocolSettingsHeader, bigSettings);
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));

  // Malformed (not a multiple of 6)
  headers.set(http2::kProtocolSettingsHeader, "AAAA");
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));

  // Two headers
  headers.set(http2::kProtocolSettingsHeader, "AAAAAAAA");
  headers.add(http2::kProtocolSettingsHeader, "AAAAAAAA");
  EXPECT_FALSE(downstreamCodec_.onIngressUpgradeMessage(req));
}

Project: proxygen

TEST_F(HTTP2CodecTest, WebsocketUpgrade) {
  HTTPMessage req = getGetRequest("/apples");
  req.setSecure(true);
  req.setEgressWebsocketUpgrade();

  upstreamCodec_.generateHeader(output_, 1, req, false);
  parse();

  EXPECT_TRUE(callbacks_.msg->isIngressWebsocketUpgrade());
  EXPECT_NE(nullptr, callbacks_.msg->getUpgradeProtocol());
  EXPECT_EQ(headers::kWebsocketString, *callbacks_.msg->getUpgradeProtocol());
}

Project: proxygen

  void addPriorityNode(HTTPCodec::StreamID id, HTTPCodec::StreamID) override {
    nodes_.push_back(id);
  }

Project: proxygen

HTTP2Codec::HTTP2Codec(TransportDirection direction)
    : HTTPParallelCodec(direction),
      headerCodec_(direction),
      frameState_(direction == TransportDirection::DOWNSTREAM
                  ? FrameState::UPSTREAM_CONNECTION_PREFACE
                  : FrameState::DOWNSTREAM_CONNECTION_PREFACE) {

  const auto maxHeaderListSize = egressSettings_.getSetting(
    SettingsId::MAX_HEADER_LIST_SIZE);
  if (maxHeaderListSize) {
    headerCodec_.setMaxUncompressed(maxHeaderListSize->value);
  }

  VLOG(4) << "creating " << getTransportDirectionString(direction)
          << " HTTP/2 codec";
}

Project: proxygen

HTTP2Codec::mapPriorityToDependency(uint8_t priority) const {
  // If the priority is out of the maximum index of virtual nodes array, we
  // return the lowest level virtual node as a punishment of not setting
  // priority correctly.
  return virtualPriorityNodes_.empty()
    ? 0
    : virtualPriorityNodes_[
        std::min(priority, uint8_t(virtualPriorityNodes_.size() - 1))];
}

Project: proxygen

TEST_F(HTTP2CodecTest, EmptyHeaderName) {
  output_.append(IOBuf::copyBuffer(kBufEmptyHeader, sizeof(kBufEmptyHeader)));
  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadHeaderValues) {
  static const std::string v1("--1");
  static const std::string v2("\13\10protocol-attack");
  static const std::string v3("\13");
  static const std::string v4("abc.com\\13\\10");
  static const vector<proxygen::compress::Header> reqHeaders = {
    Header::makeHeaderForTest(headers::kMethod, v1),
    Header::makeHeaderForTest(headers::kPath, v2),
    Header::makeHeaderForTest(headers::kScheme, v3),
    Header::makeHeaderForTest(headers::kAuthority, v4),
  };

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  HTTPCodec::StreamID stream = 1;
  for (size_t i = 0; i < reqHeaders.size(); i++, stream += 2) {
    std::vector<proxygen::compress::Header> allHeaders;
    allHeaders.push_back(reqHeaders[i]);
    auto encodedHeaders = headerCodec.encode(allHeaders);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        true,
                        true);
  }

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 4);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, OversizedFrame) {
  testFrameSizeLimit(true);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BigHeaderCompressed) {
  SetUpUpstreamTest();
  auto settings = downstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::MAX_HEADER_LIST_SIZE, 37);
  downstreamCodec_.generateSettings(output_);
  parseUpstream();

  SetUp();
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  parse();
  // session error
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadContinuation) {
  // CONTINUATION with no preceding HEADERS
  auto fakeHeaders = makeBuf(5);
  http2::writeContinuation(output_, 3, true, std::move(fakeHeaders));

  parse();
  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 1);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicHeader) {
  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  req.getHeaders().add("tab-hdr", "coolio\tv2");
  // Connection header will get dropped
  req.getHeaders().add(HTTP_HEADER_CONNECTION, "Love");
  req.setSecure(true);
  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);

  parse();
  callbacks_.expectMessage(true, 3, "/guacamole");
  EXPECT_TRUE(callbacks_.msg->isSecure());
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ("coolio", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));
  EXPECT_EQ("coolio\tv2", headers.getSingleOrEmpty("tab-hdr"));
  EXPECT_EQ("www.foo.com", headers.getSingleOrEmpty(HTTP_HEADER_HOST));
}

Project: proxygen

folly::Optional<ErrorCode> HTTP2Codec::parseHeadersDecodeFrames(
    const folly::Optional<http2::PriorityUpdate>& priority,
    const folly::Optional<uint32_t>& promisedStream,
    const folly::Optional<ExAttributes>& exAttributes,
    std::unique_ptr<HTTPMessage>& msg) {
  // decompress headers
  Cursor headerCursor(curHeaderBlock_.front());
  bool isReq = false;
  if (promisedStream) {
    isReq = true;
  } else if (exAttributes) {
    isReq = isRequest(curHeader_.stream);
  } else {
    isReq = transportDirection_ == TransportDirection::DOWNSTREAM;
  }

  // Validate circular dependencies.
  if (priority && (curHeader_.stream == priority->streamDependency)) {
    streamError(
        folly::to<string>("Circular dependency for txn=", curHeader_.stream),
        ErrorCode::PROTOCOL_ERROR,
        curHeader_.type == http2::FrameType::HEADERS);
    return ErrorCode::NO_ERROR;
  }

  decodeInfo_.init(isReq, parsingDownstreamTrailers_);
  if (priority) {
    decodeInfo_.msg->setHTTP2Priority(
        std::make_tuple(priority->streamDependency,
                        priority->exclusive,
                        priority->weight));
  }

  headerCodec_.decodeStreaming(
      headerCursor, curHeaderBlock_.chainLength(), this);
  msg = std::move(decodeInfo_.msg);
  // Saving this in case we need to log it on error
  auto g = folly::makeGuard([this] { curHeaderBlock_.move(); });
  // Check decoding error
  if (decodeInfo_.decodeError != HPACK::DecodeError::NONE) {
    static const std::string decodeErrorMessage =
        "Failed decoding header block for stream=";
    // Avoid logging header blocks that have failed decoding due to being
    // excessively large.
    if (decodeInfo_.decodeError != HPACK::DecodeError::HEADERS_TOO_LARGE) {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream
                 << " header block=";
      VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    } else {
      LOG(ERROR) << decodeErrorMessage << curHeader_.stream;
    }

    if (msg) {
      // print the partial message
      msg->dumpMessage(3);
    }
    return ErrorCode::COMPRESSION_ERROR;
  }

  // Check parsing error
  if (decodeInfo_.parsingError != "") {
    LOG(ERROR) << "Failed parsing header list for stream=" << curHeader_.stream
               << ", error=" << decodeInfo_.parsingError << ", header block=";
    VLOG(3) << IOBufPrinter::printHexFolly(curHeaderBlock_.front(), true);
    HTTPException err(HTTPException::Direction::INGRESS,
                      folly::to<std::string>("HTTP2Codec stream error: ",
                                             "stream=",
                                             curHeader_.stream,
                                             " status=",
                                             400,
                                             " error: ",
                                             decodeInfo_.parsingError));
    err.setHttpStatusCode(400);
    callback_->onError(curHeader_.stream, err, true);
    return ErrorCode::NO_ERROR;
  }

  return folly::Optional<ErrorCode>();
}

Project: proxygen

TEST_F(HTTP2CodecTest, TestMultipleDifferentContentLengthHeaders) {
  // Generate a POST request with two Content-Length headers
  // NOTE: getPostRequest already adds the content-length
  HTTPMessage req = getPostRequest();
  req.getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, "300");
  EXPECT_EQ(req.getHeaders().getNumberOfValues(HTTP_HEADER_CONTENT_LENGTH), 2);

  upstreamCodec_.generateHeader(output_, 1, req, true /* eom */);
  parse();

  // Check that the request fails before the codec finishes parsing the headers
  EXPECT_EQ(callbacks_.streamErrors, 1);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.lastParseError->getHttpStatusCode(), 400);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BadPushSettings) {
  auto settings = downstreamCodec_.getEgressSettings();
  settings->clearSettings();
  settings->setSetting(SettingsId::ENABLE_PUSH, 0);
  SetUpUpstreamTest();

  parseUpstream([&] (IOBuf* ingress) {
      EXPECT_EQ(ingress->computeChainDataLength(), http2::kFrameHeaderSize);
    });
  EXPECT_FALSE(upstreamCodec_.supportsPushTransactions());
  // Only way to disable push for downstreamCodec_ is to read
  // ENABLE_PUSH:0 from client
  EXPECT_TRUE(downstreamCodec_.supportsPushTransactions());
  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicSetting) {
  auto settings = upstreamCodec_.getEgressSettings();
  settings->setSetting(SettingsId::MAX_CONCURRENT_STREAMS, 37);
  settings->setSetting(SettingsId::INITIAL_WINDOW_SIZE, 12345);
  upstreamCodec_.generateSettings(output_);

  parse();
  EXPECT_EQ(callbacks_.settings, 1);
  EXPECT_EQ(callbacks_.maxStreams, 37);
  EXPECT_EQ(callbacks_.windowSize, 12345);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicPriority) {
  auto pri = HTTPMessage::HTTPPriority(0, true, 1);
  upstreamCodec_.generatePriority(output_, 1, pri);

  EXPECT_TRUE(parse());
  EXPECT_EQ(callbacks_.priority, pri);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

void HTTP2Codec::generateContinuation(folly::IOBufQueue& writeBuf,
                                      folly::IOBufQueue& queue,
                                      StreamID stream,
                                      size_t maxFrameSize) {
  bool endHeaders = false;
  while (!endHeaders) {
    auto chunk = queue.split(std::min(maxFrameSize, queue.chainLength()));
    endHeaders = (queue.chainLength() == 0);
    VLOG(4) << "generating CONTINUATION for stream=" << stream;
    generateHeaderCallbackWrapper(
        stream,
        http2::FrameType::CONTINUATION,
        http2::writeContinuation(
            writeBuf, stream, endHeaders, std::move(chunk)));
  }
}

Project: proxygen

TEST_F(HTTP2CodecTest, RequestFromServer) {
  // this is to test EX_HEADERS frame, which carrys the HTTP request initiated
  // by server side
  upstreamCodec_.getEgressSettings()->setSetting(
      SettingsId::ENABLE_EX_HEADERS, 1);
  SetUpUpstreamTest();
  proxygen::http2::writeSettings(
      output_, {{proxygen::SettingsId::ENABLE_EX_HEADERS, 1}});

  HTTPMessage req = getGetRequest("/guacamole");
  req.getHeaders().add(HTTP_HEADER_USER_AGENT, "coolio");
  req.getHeaders().add("tab-hdr", "coolio\tv2");
  // Connection header will get dropped
  req.getHeaders().add(HTTP_HEADER_CONNECTION, "Love");
  req.setSecure(true);

  HTTPCodec::StreamID stream = folly::Random::rand32(10, 1024) * 2;
  HTTPCodec::StreamID controlStream = folly::Random::rand32(10, 1024) * 2 + 1;
  upstreamCodec_.generateExHeader(output_, stream, req,
                                  HTTPCodec::ExAttributes(controlStream, true),
                                  true);

  parseUpstream();
  EXPECT_EQ(controlStream, callbacks_.controlStreamId);
  EXPECT_TRUE(callbacks_.isUnidirectional);
  callbacks_.expectMessage(true, 3, "/guacamole");
  EXPECT_TRUE(callbacks_.msg->isSecure());
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ("coolio", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));
  EXPECT_EQ("coolio\tv2", headers.getSingleOrEmpty("tab-hdr"));
  EXPECT_EQ("www.foo.com", headers.getSingleOrEmpty(HTTP_HEADER_HOST));
}

Project: proxygen

size_t HTTP2Codec::generatePriority(folly::IOBufQueue& writeBuf,
                                    StreamID stream,
                                    const HTTPMessage::HTTPPriority& pri) {
  VLOG(4) << "generating priority for stream=" << stream;
  if (!isStreamIngressEgressAllowed(stream)) {
    VLOG(2) << "suppressed PRIORITY for stream=" << stream
            << " ingressGoawayAck_=" << ingressGoawayAck_;
    return 0;
  }
  return generateHeaderCallbackWrapper(
            stream,
            http2::FrameType::PRIORITY,
            http2::writePriority(writeBuf, stream,
                                 {std::get<0>(pri),
                                   std::get<1>(pri),
                                   std::get<2>(pri)}));
}

Project: proxygen

TEST_F(HTTP2CodecTest, StreamIdOverflow) {
  HTTP2Codec codec(TransportDirection::UPSTREAM);

  HTTPCodec::StreamID streamId;
  codec.setNextEgressStreamId(std::numeric_limits<int32_t>::max() - 10);
  while (codec.isReusable()) {
    streamId = codec.createStream();
  }
  EXPECT_EQ(streamId, std::numeric_limits<int32_t>::max() - 2);
}

Project: proxygen

size_t HTTP2Codec::generateCertificateRequest(
    folly::IOBufQueue& writeBuf,
    uint16_t requestId,
    std::unique_ptr<folly::IOBuf> certificateRequestData) {
  VLOG(4) << "generating CERTIFICATE_REQUEST with Request-ID=" << requestId;
  return http2::writeCertificateRequest(
      writeBuf, requestId, std::move(certificateRequestData));
}

Project: proxygen

TEST_F(HTTP2CodecTest, WebsocketBadHeader) {
  const std::string kConnect{"CONNECT"};
  const std::string kWebsocketPath{"/websocket"};
  const std::string kSchemeHttps{"https"};
  vector<proxygen::compress::Header> reqHeaders = {
    Header::makeHeaderForTest(headers::kMethod, kConnect),
    Header::makeHeaderForTest(headers::kProtocol, headers::kWebsocketString),
  };
  vector<proxygen::compress::Header> optionalHeaders = {
    Header::makeHeaderForTest(headers::kPath, kWebsocketPath),
    Header::makeHeaderForTest(headers::kScheme, kSchemeHttps),
  };

  HPACKCodec headerCodec(TransportDirection::UPSTREAM);
  int stream = 1;
  for (size_t i = 0; i < optionalHeaders.size(); ++i, stream += 2) {
    auto headers = reqHeaders;
    headers.push_back(optionalHeaders[i]);
    auto encodedHeaders = headerCodec.encode(headers);
    http2::writeHeaders(output_,
                        std::move(encodedHeaders),
                        stream,
                        folly::none,
                        http2::kNoPadding,
                        false,
                        true);
    parse();
  }

  EXPECT_EQ(callbacks_.messageBegin, 0);
  EXPECT_EQ(callbacks_.headersComplete, 0);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, optionalHeaders.size());
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

size_t HTTP2Codec::generateChunkHeader(folly::IOBufQueue& /*writeBuf*/,
                                       StreamID /*stream*/,
                                       size_t /*length*/) {
  // HTTP/2 has no chunk headers
  return 0;
}

Project: proxygen

std::string base64url_encode(ByteRange range) {
  return proxygen::Base64::urlEncode(range);
}

Project: proxygen

TEST_F(HTTP2CodecTest, BasicContinuation) {
  HTTPMessage req = getBigGetRequest();
  upstreamCodec_.generateHeader(output_, 1, req);

  parse();
  callbacks_.expectMessage(false, -1, "/");
#ifndef NDEBUG
  EXPECT_GT(downstreamCodec_.getReceivedFrameCount(), 1);
#endif
  const auto& headers = callbacks_.msg->getHeaders();
  EXPECT_EQ("coolio", headers.getSingleOrEmpty(HTTP_HEADER_USER_AGENT));
  EXPECT_EQ(callbacks_.messageBegin, 1);
  EXPECT_EQ(callbacks_.headersComplete, 1);
  EXPECT_EQ(callbacks_.messageComplete, 0);
  EXPECT_EQ(callbacks_.streamErrors, 0);
  EXPECT_EQ(callbacks_.sessionErrors, 0);
}

Project: proxygen

size_t HTTP2Codec::generateConnectionPreface(folly::IOBufQueue& writeBuf) {
  if (transportDirection_ == TransportDirection::UPSTREAM) {
    VLOG(4) << "generating connection preface";
    writeBuf.append(http2::kConnectionPreface);
    return http2::kConnectionPreface.length();
  }
  return 0;
}