blob: be6a162452267d18ac51d2aa8d34e4803871b5af [file] [log] [blame]
// Copyright 2013 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 "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
#include <memory>
#include <string>
#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
using spdy::SpdyHeaderBlock;
using testing::_;
using testing::StrictMock;
namespace quic {
namespace test {
namespace {
class MockQuicSpdyClientSession : public QuicSpdyClientSession {
public:
explicit MockQuicSpdyClientSession(
const ParsedQuicVersionVector& supported_versions,
QuicConnection* connection,
QuicClientPushPromiseIndex* push_promise_index)
: QuicSpdyClientSession(DefaultQuicConfig(),
supported_versions,
connection,
QuicServerId("example.com", 443, false),
&crypto_config_,
push_promise_index),
crypto_config_(crypto_test_utils::ProofVerifierForTesting()) {}
MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
delete;
~MockQuicSpdyClientSession() override = default;
MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
private:
QuicCryptoClientConfig crypto_config_;
};
class QuicSpdyClientStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
public:
class StreamVisitor;
QuicSpdyClientStreamTest()
: connection_(
new StrictMock<MockQuicConnection>(&helper_,
&alarm_factory_,
Perspective::IS_CLIENT,
SupportedVersions(GetParam()))),
session_(connection_->supported_versions(),
connection_,
&push_promise_index_),
body_("hello world") {
SetQuicReloadableFlag(quic_supports_tls_handshake, true);
session_.Initialize();
headers_[":status"] = "200";
headers_["content-length"] = "11";
stream_ = std::make_unique<QuicSpdyClientStream>(
GetNthClientInitiatedBidirectionalStreamId(
connection_->transport_version(), 0),
&session_, BIDIRECTIONAL);
stream_visitor_ = std::make_unique<StreamVisitor>();
stream_->set_visitor(stream_visitor_.get());
}
class StreamVisitor : public QuicSpdyClientStream::Visitor {
void OnClose(QuicSpdyStream* stream) override {
QUIC_DVLOG(1) << "stream " << stream->id();
}
};
MockQuicConnectionHelper helper_;
MockAlarmFactory alarm_factory_;
StrictMock<MockQuicConnection>* connection_;
QuicClientPushPromiseIndex push_promise_index_;
MockQuicSpdyClientSession session_;
std::unique_ptr<QuicSpdyClientStream> stream_;
std::unique_ptr<StreamVisitor> stream_visitor_;
SpdyHeaderBlock headers_;
std::string body_;
HttpEncoder encoder_;
};
INSTANTIATE_TEST_SUITE_P(Tests,
QuicSpdyClientStreamTest,
::testing::ValuesIn(AllSupportedVersions()));
TEST_P(QuicSpdyClientStreamTest, TestReceivingIllegalResponseStatusCode) {
headers_[":status"] = "200 ok";
EXPECT_CALL(*connection_, SendControlFrame(_));
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
EXPECT_EQ(QUIC_BAD_APPLICATION_PAYLOAD, stream_->stream_error());
}
TEST_P(QuicSpdyClientStreamTest, TestFraming) {
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
std::unique_ptr<char[]> buffer;
QuicByteCount header_length =
encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
std::string header = std::string(buffer.get(), header_length);
std::string data = VersionHasDataFrameHeader(connection_->transport_version())
? header + body_
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
}
TEST_P(QuicSpdyClientStreamTest, TestFraming100Continue) {
headers_[":status"] = "100";
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, body_));
EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
EXPECT_EQ(0u, stream_->response_headers().size());
EXPECT_EQ(100, stream_->response_code());
EXPECT_EQ("", stream_->data());
}
TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
std::unique_ptr<char[]> buffer;
QuicByteCount header_length =
encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
std::string header = std::string(buffer.get(), header_length);
std::string data = VersionHasDataFrameHeader(connection_->transport_version())
? header + body_
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
EXPECT_EQ(body_, stream_->data());
}
TEST_P(QuicSpdyClientStreamTest,
QUIC_TEST_DISABLED_IN_CHROME(TestFramingExtraData)) {
std::string large_body = "hello world!!!!!!";
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
// The headers should parse successfully.
EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
EXPECT_EQ(200, stream_->response_code());
std::unique_ptr<char[]> buffer;
QuicByteCount header_length =
encoder_.SerializeDataFrameHeader(large_body.length(), &buffer);
std::string header = std::string(buffer.get(), header_length);
std::string data = VersionHasDataFrameHeader(connection_->transport_version())
? header + large_body
: large_body;
EXPECT_CALL(*connection_, SendControlFrame(_));
EXPECT_CALL(*connection_,
OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
}
// Test that receiving trailing headers (on the headers stream), containing a
// final offset, results in the stream being closed at that byte offset.
TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) {
// There is no kFinalOffsetHeaderKey if trailers are sent on the
// request/response stream.
if (VersionUsesQpack(connection_->transport_version())) {
return;
}
// Send headers as usual.
auto headers = AsHeaderList(headers_);
stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
headers);
// Send trailers before sending the body. Even though a FIN has been received
// the stream should not be closed, as it does not yet have all the data bytes
// promised by the final offset field.
SpdyHeaderBlock trailer_block;
trailer_block["trailer key"] = "trailer value";
trailer_block[kFinalOffsetHeaderKey] =
QuicTextUtils::Uint64ToString(body_.size());
auto trailers = AsHeaderList(trailer_block);
stream_->OnStreamHeaderList(true, trailers.uncompressed_header_bytes(),
trailers);
// Now send the body, which should close the stream as the FIN has been
// received, as well as all data.
std::unique_ptr<char[]> buffer;
QuicByteCount header_length =
encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
std::string header = std::string(buffer.get(), header_length);
std::string data = VersionHasDataFrameHeader(connection_->transport_version())
? header + body_
: body_;
stream_->OnStreamFrame(
QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
EXPECT_TRUE(stream_->reading_stopped());
}
} // namespace
} // namespace test
} // namespace quic