blob: b761a195f46673414ed4ca96299543dd75d5a2a1 [file] [log] [blame]
// Copyright 2014 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 "quic/core/http/quic_spdy_client_session_base.h"
#include <string>
#include "quic/core/http/quic_client_promised_info.h"
#include "quic/core/http/spdy_server_push_utils.h"
#include "quic/core/quic_utils.h"
#include "quic/platform/api/quic_flags.h"
#include "quic/platform/api/quic_logging.h"
using spdy::SpdyHeaderBlock;
namespace quic {
QuicSpdyClientSessionBase::QuicSpdyClientSessionBase(
QuicConnection* connection,
QuicClientPushPromiseIndex* push_promise_index,
const QuicConfig& config,
const ParsedQuicVersionVector& supported_versions)
: QuicSpdySession(connection, nullptr, config, supported_versions),
push_promise_index_(push_promise_index),
largest_promised_stream_id_(
QuicUtils::GetInvalidStreamId(connection->transport_version())) {}
QuicSpdyClientSessionBase::~QuicSpdyClientSessionBase() {
// all promised streams for this session
for (auto& it : promised_by_id_) {
QUIC_DVLOG(1) << "erase stream " << it.first << " url " << it.second->url();
push_promise_index_->promised_by_url()->erase(it.second->url());
}
DeleteConnection();
}
void QuicSpdyClientSessionBase::OnConfigNegotiated() {
QuicSpdySession::OnConfigNegotiated();
}
void QuicSpdyClientSessionBase::OnInitialHeadersComplete(
QuicStreamId stream_id,
const SpdyHeaderBlock& response_headers) {
// Note that the strong ordering of the headers stream means that
// QuicSpdyClientStream::OnPromiseHeadersComplete must have already
// been called (on the associated stream) if this is a promised
// stream. However, this stream may not have existed at this time,
// hence the need to query the session.
QuicClientPromisedInfo* promised = GetPromisedById(stream_id);
if (!promised)
return;
promised->OnResponseHeaders(response_headers);
}
void QuicSpdyClientSessionBase::OnPromiseHeaderList(
QuicStreamId stream_id,
QuicStreamId promised_stream_id,
size_t frame_len,
const QuicHeaderList& header_list) {
if (IsStaticStream(stream_id)) {
connection()->CloseConnection(
QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
// In HTTP3, push promises are received on individual streams, so they could
// be arrive out of order.
if (!VersionUsesHttp3(transport_version()) &&
promised_stream_id !=
QuicUtils::GetInvalidStreamId(transport_version()) &&
largest_promised_stream_id_ !=
QuicUtils::GetInvalidStreamId(transport_version()) &&
promised_stream_id <= largest_promised_stream_id_) {
connection()->CloseConnection(
QUIC_INVALID_STREAM_ID,
"Received push stream id lesser or equal to the"
" last accepted before",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
if (!IsIncomingStream(promised_stream_id)) {
connection()->CloseConnection(
QUIC_INVALID_STREAM_ID, "Received push stream id for outgoing stream.",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
if (VersionUsesHttp3(transport_version())) {
// Received push stream id is higher than MAX_PUSH_ID
// because no MAX_PUSH_ID frame is ever sent.
connection()->CloseConnection(
QUIC_INVALID_STREAM_ID,
"Received push stream id higher than MAX_PUSH_ID.",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
largest_promised_stream_id_ = promised_stream_id;
QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
if (!stream) {
// It's quite possible to receive headers after a stream has been reset.
return;
}
stream->OnPromiseHeaderList(promised_stream_id, frame_len, header_list);
}
bool QuicSpdyClientSessionBase::HandlePromised(QuicStreamId /* associated_id */,
QuicStreamId promised_id,
const SpdyHeaderBlock& headers) {
// TODO(b/136295430): Do not treat |promised_id| as a stream ID when using
// IETF QUIC.
// Due to pathalogical packet re-ordering, it is possible that
// frames for the promised stream have already arrived, and the
// promised stream could be active or closed.
if (IsClosedStream(promised_id)) {
// There was a RST on the data stream already, perhaps
// QUIC_REFUSED_STREAM?
QUIC_DVLOG(1) << "Promise ignored for stream " << promised_id
<< " that is already closed";
return false;
}
if (push_promise_index_->promised_by_url()->size() >= get_max_promises()) {
QUIC_DVLOG(1) << "Too many promises, rejecting promise for stream "
<< promised_id;
ResetPromised(promised_id, QUIC_REFUSED_STREAM);
return false;
}
const std::string url =
SpdyServerPushUtils::GetPromisedUrlFromHeaders(headers);
QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url);
if (old_promised) {
QUIC_DVLOG(1) << "Promise for stream " << promised_id
<< " is duplicate URL " << url
<< " of previous promise for stream " << old_promised->id();
ResetPromised(promised_id, QUIC_DUPLICATE_PROMISE_URL);
return false;
}
if (GetPromisedById(promised_id)) {
// OnPromiseHeadersComplete() would have closed the connection if
// promised id is a duplicate.
QUIC_BUG(quic_bug_10412_1) << "Duplicate promise for id " << promised_id;
return false;
}
QuicClientPromisedInfo* promised =
new QuicClientPromisedInfo(this, promised_id, url);
std::unique_ptr<QuicClientPromisedInfo> promised_owner(promised);
promised->Init();
QUIC_DVLOG(1) << "stream " << promised_id << " emplace url " << url;
(*push_promise_index_->promised_by_url())[url] = promised;
promised_by_id_[promised_id] = std::move(promised_owner);
bool result = promised->OnPromiseHeaders(headers);
if (result) {
QUICHE_DCHECK(promised_by_id_.find(promised_id) != promised_by_id_.end());
}
return result;
}
QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedByUrl(
const std::string& url) {
auto it = push_promise_index_->promised_by_url()->find(url);
if (it != push_promise_index_->promised_by_url()->end()) {
return it->second;
}
return nullptr;
}
QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedById(
const QuicStreamId id) {
auto it = promised_by_id_.find(id);
if (it != promised_by_id_.end()) {
return it->second.get();
}
return nullptr;
}
QuicSpdyStream* QuicSpdyClientSessionBase::GetPromisedStream(
const QuicStreamId id) {
QuicStream* stream = GetActiveStream(id);
if (stream != nullptr) {
return static_cast<QuicSpdyStream*>(stream);
}
return nullptr;
}
void QuicSpdyClientSessionBase::DeletePromised(
QuicClientPromisedInfo* promised) {
push_promise_index_->promised_by_url()->erase(promised->url());
// Since promised_by_id_ contains the unique_ptr, this will destroy
// promised.
// ToDo: Consider implementing logic to send a new MAX_PUSH_ID frame to allow
// another stream to be promised.
promised_by_id_.erase(promised->id());
if (!VersionUsesHttp3(transport_version())) {
headers_stream()->MaybeReleaseSequencerBuffer();
}
}
void QuicSpdyClientSessionBase::OnPushStreamTimedOut(
QuicStreamId /*stream_id*/) {}
void QuicSpdyClientSessionBase::ResetPromised(
QuicStreamId id,
QuicRstStreamErrorCode error_code) {
QUICHE_DCHECK(QuicUtils::IsServerInitiatedStreamId(transport_version(), id));
ResetStream(id, error_code);
if (!IsOpenStream(id) && !IsClosedStream(id)) {
MaybeIncreaseLargestPeerStreamId(id);
}
}
void QuicSpdyClientSessionBase::OnStreamClosed(QuicStreamId stream_id) {
QuicSpdySession::OnStreamClosed(stream_id);
if (!VersionUsesHttp3(transport_version())) {
headers_stream()->MaybeReleaseSequencerBuffer();
}
}
bool QuicSpdyClientSessionBase::ShouldReleaseHeadersStreamSequencerBuffer() {
return !HasActiveRequestStreams() && promised_by_id_.empty();
}
bool QuicSpdyClientSessionBase::ShouldKeepConnectionAlive() const {
return QuicSpdySession::ShouldKeepConnectionAlive() ||
num_outgoing_draining_streams() > 0;
}
bool QuicSpdyClientSessionBase::OnSettingsFrame(const SettingsFrame& frame) {
if (!was_zero_rtt_rejected()) {
if (max_outbound_header_list_size() != std::numeric_limits<size_t>::max() &&
frame.values.find(SETTINGS_MAX_FIELD_SECTION_SIZE) ==
frame.values.end()) {
CloseConnectionWithDetails(
QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
"Server accepted 0-RTT but omitted non-default "
"SETTINGS_MAX_FIELD_SECTION_SIZE");
return false;
}
if (qpack_encoder()->maximum_blocked_streams() != 0 &&
frame.values.find(SETTINGS_QPACK_BLOCKED_STREAMS) ==
frame.values.end()) {
CloseConnectionWithDetails(
QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
"Server accepted 0-RTT but omitted non-default "
"SETTINGS_QPACK_BLOCKED_STREAMS");
return false;
}
if (qpack_encoder()->MaximumDynamicTableCapacity() != 0 &&
frame.values.find(SETTINGS_QPACK_MAX_TABLE_CAPACITY) ==
frame.values.end()) {
CloseConnectionWithDetails(
QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
"Server accepted 0-RTT but omitted non-default "
"SETTINGS_QPACK_MAX_TABLE_CAPACITY");
return false;
}
}
if (!QuicSpdySession::OnSettingsFrame(frame)) {
return false;
}
std::unique_ptr<char[]> buffer;
QuicByteCount frame_length =
HttpEncoder::SerializeSettingsFrame(frame, &buffer);
auto serialized_data = std::make_unique<ApplicationState>(
buffer.get(), buffer.get() + frame_length);
GetMutableCryptoStream()->SetServerApplicationStateForResumption(
std::move(serialized_data));
return true;
}
} // namespace quic