blob: 20e96c6c68c54b6e4d0761f2b1f5ad656ad1a823 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/core/http/quic_send_control_stream.h"
#include <utility>
#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/crypto/null_encrypter.h"
#include "quiche/quic/platform/api/quic_flags.h"
#include "quiche/quic/test_tools/quic_config_peer.h"
#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
namespace quic {
namespace test {
namespace {
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Invoke;
using ::testing::StrictMock;
struct TestParams {
TestParams(const ParsedQuicVersion& version, Perspective perspective)
: version(version), perspective(perspective) {
QUIC_LOG(INFO) << "TestParams: " << *this;
}
TestParams(const TestParams& other)
: version(other.version), perspective(other.perspective) {}
friend std::ostream& operator<<(std::ostream& os, const TestParams& tp) {
os << "{ version: " << ParsedQuicVersionToString(tp.version)
<< ", perspective: "
<< (tp.perspective == Perspective::IS_CLIENT ? "client" : "server")
<< "}";
return os;
}
ParsedQuicVersion version;
Perspective perspective;
};
// Used by ::testing::PrintToStringParamName().
std::string PrintToString(const TestParams& tp) {
return absl::StrCat(
ParsedQuicVersionToString(tp.version), "_",
(tp.perspective == Perspective::IS_CLIENT ? "client" : "server"));
}
std::vector<TestParams> GetTestParams() {
std::vector<TestParams> params;
ParsedQuicVersionVector all_supported_versions = AllSupportedVersions();
for (const auto& version : AllSupportedVersions()) {
if (!VersionUsesHttp3(version.transport_version)) {
continue;
}
for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
params.emplace_back(version, p);
}
}
return params;
}
class QuicSendControlStreamTest : public QuicTestWithParam<TestParams> {
public:
QuicSendControlStreamTest()
: connection_(new StrictMock<MockQuicConnection>(
&helper_, &alarm_factory_, perspective(),
SupportedVersions(GetParam().version))),
session_(connection_) {
ON_CALL(session_, WritevData(_, _, _, _, _, _))
.WillByDefault(Invoke(&session_, &MockQuicSpdySession::ConsumeData));
}
void Initialize() {
EXPECT_CALL(session_, OnCongestionWindowChange(_)).Times(AnyNumber());
session_.Initialize();
connection_->SetEncrypter(
ENCRYPTION_FORWARD_SECURE,
std::make_unique<NullEncrypter>(connection_->perspective()));
send_control_stream_ = QuicSpdySessionPeer::GetSendControlStream(&session_);
QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
session_.config(), kMinimumFlowControlSendWindow);
QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
session_.config(), kMinimumFlowControlSendWindow);
QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(session_.config(), 3);
session_.OnConfigNegotiated();
}
Perspective perspective() const { return GetParam().perspective; }
MockQuicConnectionHelper helper_;
MockAlarmFactory alarm_factory_;
StrictMock<MockQuicConnection>* connection_;
StrictMock<MockQuicSpdySession> session_;
QuicSendControlStream* send_control_stream_;
};
INSTANTIATE_TEST_SUITE_P(Tests, QuicSendControlStreamTest,
::testing::ValuesIn(GetTestParams()),
::testing::PrintToStringParamName());
TEST_P(QuicSendControlStreamTest, WriteSettings) {
SetQuicFlag(quic_enable_http3_grease_randomness, false);
session_.set_qpack_maximum_dynamic_table_capacity(255);
session_.set_qpack_maximum_blocked_streams(16);
session_.set_max_inbound_header_list_size(1024);
Initialize();
testing::InSequence s;
std::string expected_write_data = absl::HexStringToBytes(
"00" // stream type: control stream
"04" // frame type: SETTINGS frame
"0b" // frame length
"01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
"40ff" // 255
"06" // SETTINGS_MAX_HEADER_LIST_SIZE
"4400" // 1024
"07" // SETTINGS_QPACK_BLOCKED_STREAMS
"10" // 16
"4040" // 0x40 as the reserved settings id
"14" // 20
"4040" // 0x40 as the reserved frame type
"01" // 1 byte frame length
"61"); // payload "a"
if ((!GetQuicReloadableFlag(quic_verify_request_headers_2) ||
perspective() == Perspective::IS_CLIENT) &&
QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
HttpDatagramSupport::kDraft04) {
expected_write_data = absl::HexStringToBytes(
"00" // stream type: control stream
"04" // frame type: SETTINGS frame
"0b" // frame length
"01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
"40ff" // 255
"06" // SETTINGS_MAX_HEADER_LIST_SIZE
"4400" // 1024
"07" // SETTINGS_QPACK_BLOCKED_STREAMS
"10" // 16
"4040" // 0x40 as the reserved settings id
"14" // 20
"800ffd277" // SETTINGS_H3_DATAGRAM_DRAFT04
"01" // 1
"4040" // 0x40 as the reserved frame type
"01" // 1 byte frame length
"61"); // payload "a"
}
if (GetQuicReloadableFlag(quic_verify_request_headers_2) &&
perspective() == Perspective::IS_SERVER &&
QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) ==
HttpDatagramSupport::kNone) {
expected_write_data = absl::HexStringToBytes(
"00" // stream type: control stream
"04" // frame type: SETTINGS frame
"0d" // frame length
"01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
"40ff" // 255
"06" // SETTINGS_MAX_HEADER_LIST_SIZE
"4400" // 1024
"07" // SETTINGS_QPACK_BLOCKED_STREAMS
"10" // 16
"08" // SETTINGS_ENABLE_CONNECT_PROTOCOL
"01" // 1
"4040" // 0x40 as the reserved settings id
"14" // 20
"4040" // 0x40 as the reserved frame type
"01" // 1 byte frame length
"61"); // payload "a"
}
if (GetQuicReloadableFlag(quic_verify_request_headers_2) &&
perspective() == Perspective::IS_SERVER &&
QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) !=
HttpDatagramSupport::kNone) {
expected_write_data = absl::HexStringToBytes(
"00" // stream type: control stream
"04" // frame type: SETTINGS frame
"0e" // frame length
"01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
"40ff" // 255
"06" // SETTINGS_MAX_HEADER_LIST_SIZE
"4400" // 1024
"07" // SETTINGS_QPACK_BLOCKED_STREAMS
"10" // 16
"08" // SETTINGS_ENABLE_CONNECT_PROTOCOL
"01" // 1
"4040" // 0x40 as the reserved settings id
"14" // 20
"800ffd277" // SETTINGS_H3_DATAGRAM_DRAFT04
"01" // 1
"4040" // 0x40 as the reserved frame type
"01" // 1 byte frame length
"61"); // payload "a"
}
auto buffer = std::make_unique<char[]>(expected_write_data.size());
QuicDataWriter writer(expected_write_data.size(), buffer.get());
// A lambda to save and consume stream data when QuicSession::WritevData() is
// called.
auto save_write_data =
[&writer, this](QuicStreamId /*id*/, size_t write_length,
QuicStreamOffset offset, StreamSendingState /*state*/,
TransmissionType /*type*/,
absl::optional<EncryptionLevel> /*level*/) {
send_control_stream_->WriteStreamData(offset, write_length, &writer);
return QuicConsumedData(/* bytes_consumed = */ write_length,
/* fin_consumed = */ false);
};
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), 1, _, _, _, _))
.WillOnce(Invoke(save_write_data));
EXPECT_CALL(session_, WritevData(send_control_stream_->id(),
expected_write_data.size() - 5, _, _, _, _))
.WillOnce(Invoke(save_write_data));
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), 4, _, _, _, _))
.WillOnce(Invoke(save_write_data));
send_control_stream_->MaybeSendSettingsFrame();
quiche::test::CompareCharArraysWithHexError(
"settings", writer.data(), writer.length(), expected_write_data.data(),
expected_write_data.length());
}
TEST_P(QuicSendControlStreamTest, WriteSettingsOnlyOnce) {
Initialize();
testing::InSequence s;
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), 1, _, _, _, _));
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _))
.Times(2);
send_control_stream_->MaybeSendSettingsFrame();
// No data should be written the second time MaybeSendSettingsFrame() is
// called.
send_control_stream_->MaybeSendSettingsFrame();
}
// Send stream type and SETTINGS frame if WritePriorityUpdate() is called first.
TEST_P(QuicSendControlStreamTest, WritePriorityBeforeSettings) {
Initialize();
testing::InSequence s;
// The first write will trigger the control stream to write stream type, a
// SETTINGS frame, and a greased frame before the PRIORITY_UPDATE frame.
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _))
.Times(4);
send_control_stream_->WritePriorityUpdate(
/* stream_id = */ 0,
QuicStreamPriority{/* urgency = */ 3, /* incremental = */ false});
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&session_));
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _));
send_control_stream_->WritePriorityUpdate(
/* stream_id = */ 0,
QuicStreamPriority{/* urgency = */ 3, /* incremental = */ false});
}
TEST_P(QuicSendControlStreamTest, CloseControlStream) {
Initialize();
EXPECT_CALL(*connection_,
CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
send_control_stream_->OnStopSending(
QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED));
}
TEST_P(QuicSendControlStreamTest, ReceiveDataOnSendControlStream) {
Initialize();
QuicStreamFrame frame(send_control_stream_->id(), false, 0, "test");
EXPECT_CALL(
*connection_,
CloseConnection(QUIC_DATA_RECEIVED_ON_WRITE_UNIDIRECTIONAL_STREAM, _, _));
send_control_stream_->OnStreamFrame(frame);
}
TEST_P(QuicSendControlStreamTest, SendGoAway) {
Initialize();
StrictMock<MockHttp3DebugVisitor> debug_visitor;
session_.set_debug_visitor(&debug_visitor);
QuicStreamId stream_id = 4;
EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _))
.Times(AnyNumber());
EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_));
EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(stream_id));
send_control_stream_->SendGoAway(stream_id);
}
} // namespace
} // namespace test
} // namespace quic