blob: 37e5dd5851f58af5b10768891ec10f2041e0abbf [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 "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
#include <string>
#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
#include "net/third_party/quiche/src/quic/core/http/spdy_server_push_utils.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/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());
}
delete connection();
}
void QuicSpdyClientSessionBase::OnConfigNegotiated() {
QuicSpdySession::OnConfigNegotiated();
}
void QuicSpdyClientSessionBase::OnCryptoHandshakeEvent(
CryptoHandshakeEvent event) {
QuicSpdySession::OnCryptoHandshakeEvent(event);
if (event == HANDSHAKE_CONFIRMED && max_allowed_push_id() > 0 &&
VersionHasIetfQuicFrames(transport_version())) {
SendMaxPushId(max_allowed_push_id());
}
}
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;
}
if (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 (VersionHasIetfQuicFrames(transport_version()) &&
promised_stream_id > max_allowed_push_id()) {
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 = GetSpdyDataStream(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) {
// 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 << "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) {
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) {
StreamMap::iterator it = stream_map().find(id);
if (it != stream_map().end()) {
return static_cast<QuicSpdyStream*>(it->second.get());
}
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 (!VersionUsesQpack(transport_version())) {
headers_stream()->MaybeReleaseSequencerBuffer();
}
}
void QuicSpdyClientSessionBase::OnPushStreamTimedOut(
QuicStreamId /*stream_id*/) {}
void QuicSpdyClientSessionBase::ResetPromised(
QuicStreamId id,
QuicRstStreamErrorCode error_code) {
SendRstStream(id, error_code, 0);
if (!IsOpenStream(id)) {
MaybeIncreaseLargestPeerStreamId(id);
}
}
void QuicSpdyClientSessionBase::CloseStreamInner(QuicStreamId stream_id,
bool locally_reset) {
QuicSpdySession::CloseStreamInner(stream_id, locally_reset);
if (!VersionUsesQpack(transport_version())) {
headers_stream()->MaybeReleaseSequencerBuffer();
}
}
bool QuicSpdyClientSessionBase::ShouldReleaseHeadersStreamSequencerBuffer() {
return !HasActiveRequestStreams() && promised_by_id_.empty();
}
} // namespace quic