|  | // Copyright 2017 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/spdy/http2_push_promise_index.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/trace_event/memory_usage_estimator.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | Http2PushPromiseIndex::Http2PushPromiseIndex() = default; | 
|  |  | 
|  | Http2PushPromiseIndex::~Http2PushPromiseIndex() { | 
|  | DCHECK(unclaimed_pushed_streams_.empty()); | 
|  | } | 
|  |  | 
|  | bool Http2PushPromiseIndex::RegisterUnclaimedPushedStream( | 
|  | const GURL& url, | 
|  | spdy::SpdyStreamId stream_id, | 
|  | Delegate* delegate) { | 
|  | DCHECK(!url.is_empty()); | 
|  | DCHECK_GT(stream_id, kNoPushedStreamFound); | 
|  | DCHECK(delegate); | 
|  |  | 
|  | // Find the entry with |url| for |delegate| if such exists (there can be at | 
|  | // most one such entry).  It is okay to cast away const from |delegate|, | 
|  | // because it is only used for lookup. | 
|  | auto it = unclaimed_pushed_streams_.lower_bound(UnclaimedPushedStream{ | 
|  | url, const_cast<Delegate*>(delegate), kNoPushedStreamFound}); | 
|  | // If such entry is found, do not allow registering another one. | 
|  | if (it != unclaimed_pushed_streams_.end() && it->url == url && | 
|  | it->delegate == delegate) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | unclaimed_pushed_streams_.insert( | 
|  | it, UnclaimedPushedStream{url, delegate, stream_id}); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Http2PushPromiseIndex::UnregisterUnclaimedPushedStream( | 
|  | const GURL& url, | 
|  | spdy::SpdyStreamId stream_id, | 
|  | Delegate* delegate) { | 
|  | DCHECK(!url.is_empty()); | 
|  | DCHECK_GT(stream_id, kNoPushedStreamFound); | 
|  | DCHECK(delegate); | 
|  |  | 
|  | size_t result = unclaimed_pushed_streams_.erase( | 
|  | UnclaimedPushedStream{url, delegate, stream_id}); | 
|  |  | 
|  | return result == 1; | 
|  | } | 
|  |  | 
|  | // The runtime of this method is linear in unclaimed_pushed_streams_.size(), | 
|  | // which is acceptable, because it is only used in NetLog, tests, and DCHECKs. | 
|  | size_t Http2PushPromiseIndex::CountStreamsForSession( | 
|  | const Delegate* delegate) const { | 
|  | DCHECK(delegate); | 
|  |  | 
|  | return std::count_if(unclaimed_pushed_streams_.begin(), | 
|  | unclaimed_pushed_streams_.end(), | 
|  | [&delegate](const UnclaimedPushedStream& entry) { | 
|  | return entry.delegate == delegate; | 
|  | }); | 
|  | } | 
|  |  | 
|  | spdy::SpdyStreamId Http2PushPromiseIndex::FindStream( | 
|  | const GURL& url, | 
|  | const Delegate* delegate) const { | 
|  | // Find the entry with |url| for |delegate| if such exists (there can be at | 
|  | // most one such entry).  It is okay to cast away const from |delegate|, | 
|  | // because it is only used for lookup. | 
|  | auto it = unclaimed_pushed_streams_.lower_bound(UnclaimedPushedStream{ | 
|  | url, const_cast<Delegate*>(delegate), kNoPushedStreamFound}); | 
|  |  | 
|  | if (it == unclaimed_pushed_streams_.end() || it->url != url || | 
|  | it->delegate != delegate) { | 
|  | return kNoPushedStreamFound; | 
|  | } | 
|  |  | 
|  | return it->stream_id; | 
|  | } | 
|  |  | 
|  | void Http2PushPromiseIndex::ClaimPushedStream( | 
|  | const SpdySessionKey& key, | 
|  | const GURL& url, | 
|  | const HttpRequestInfo& request_info, | 
|  | base::WeakPtr<SpdySession>* session, | 
|  | spdy::SpdyStreamId* stream_id) { | 
|  | DCHECK(!url.is_empty()); | 
|  |  | 
|  | *session = nullptr; | 
|  | *stream_id = kNoPushedStreamFound; | 
|  |  | 
|  | // Find the first entry for |url|, if such exists. | 
|  | auto it = unclaimed_pushed_streams_.lower_bound( | 
|  | UnclaimedPushedStream{url, nullptr, kNoPushedStreamFound}); | 
|  |  | 
|  | while (it != unclaimed_pushed_streams_.end() && it->url == url) { | 
|  | if (it->delegate->ValidatePushedStream(it->stream_id, url, request_info, | 
|  | key)) { | 
|  | *session = it->delegate->GetWeakPtrToSession(); | 
|  | *stream_id = it->stream_id; | 
|  | unclaimed_pushed_streams_.erase(it); | 
|  | return; | 
|  | } | 
|  | ++it; | 
|  | } | 
|  | } | 
|  |  | 
|  | size_t Http2PushPromiseIndex::EstimateMemoryUsage() const { | 
|  | return base::trace_event::EstimateMemoryUsage(unclaimed_pushed_streams_); | 
|  | } | 
|  |  | 
|  | size_t Http2PushPromiseIndex::UnclaimedPushedStream::EstimateMemoryUsage() | 
|  | const { | 
|  | return base::trace_event::EstimateMemoryUsage(url) + | 
|  | sizeof(spdy::SpdyStreamId) + sizeof(Delegate*); | 
|  | } | 
|  |  | 
|  | bool Http2PushPromiseIndex::CompareByUrl::operator()( | 
|  | const UnclaimedPushedStream& a, | 
|  | const UnclaimedPushedStream& b) const { | 
|  | // Compare by URL first. | 
|  | if (a.url < b.url) | 
|  | return true; | 
|  | if (a.url > b.url) | 
|  | return false; | 
|  | // For identical URL, put an entry with delegate == nullptr first. | 
|  | // The C++ standard dictates that comparisons between |nullptr| and other | 
|  | // pointers are unspecified, hence the need to handle this case separately. | 
|  | if (a.delegate == nullptr && b.delegate != nullptr) { | 
|  | return true; | 
|  | } | 
|  | if (a.delegate != nullptr && b.delegate == nullptr) { | 
|  | return false; | 
|  | } | 
|  | // Then compare by Delegate. | 
|  | // The C++ standard guarantees that both |nullptr < nullptr| and | 
|  | // |nullptr > nullptr| are false, so there is no need to handle that case | 
|  | // separately. | 
|  | if (a.delegate < b.delegate) | 
|  | return true; | 
|  | if (a.delegate > b.delegate) | 
|  | return false; | 
|  | // If URL and Delegate are identical, then compare by stream ID. | 
|  | return a.stream_id < b.stream_id; | 
|  | } | 
|  |  | 
|  | }  // namespace net |