diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/Http3Stream.cpp | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp new file mode 100644 index 0000000000..4f33ca07e2 --- /dev/null +++ b/netwerk/protocol/http/Http3Stream.cpp @@ -0,0 +1,533 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" +#include "Http3Session.h" +#include "Http3Stream.h" +#include "nsHttpRequestHead.h" +#include "nsHttpTransaction.h" +#include "nsIClassOfService.h" +#include "nsISocketTransport.h" +#include "nsSocketTransportService2.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "nsIOService.h" +#include "nsHttpHandler.h" + +#include <stdio.h> + +namespace mozilla { +namespace net { + +Http3StreamBase::Http3StreamBase(nsAHttpTransaction* trans, + Http3Session* session) + : mTransaction(trans), mSession(session) {} + +Http3StreamBase::~Http3StreamBase() = default; + +Http3Stream::Http3Stream(nsAHttpTransaction* httpTransaction, + Http3Session* session, const ClassOfService& cos, + uint64_t currentBrowserId) + : Http3StreamBase(httpTransaction, session), + mCurrentBrowserId(currentBrowserId) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + LOG3(("Http3Stream::Http3Stream [this=%p]", this)); + + nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); + if (trans) { + mTransactionBrowserId = trans->BrowserId(); + } + + SetPriority(cos.Flags()); + SetIncremental(cos.Incremental()); +} + +void Http3Stream::Close(nsresult aResult) { + mRecvState = RECV_DONE; + mTransaction->Close(aResult); + // Clear the mSession to break the cycle. + mSession = nullptr; +} + +bool Http3Stream::GetHeadersString(const char* buf, uint32_t avail, + uint32_t* countUsed) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + LOG3(("Http3Stream::GetHeadersString %p avail=%u.", this, avail)); + + mFlatHttpRequestHeaders.Append(buf, avail); + // We can use the simple double crlf because firefox is the + // only client we are parsing + int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); + + if (endHeader == kNotFound) { + // We don't have all the headers yet + LOG3( + ("Http3Stream::GetHeadersString %p " + "Need more header bytes. Len = %zu", + this, mFlatHttpRequestHeaders.Length())); + *countUsed = avail; + return false; + } + + uint32_t oldLen = mFlatHttpRequestHeaders.Length(); + mFlatHttpRequestHeaders.SetLength(endHeader + 2); + *countUsed = avail - (oldLen - endHeader) + 4; + + return true; +} + +void Http3Stream::SetPriority(uint32_t aCos) { + if (aCos & nsIClassOfService::UrgentStart) { + // coming from an user interaction => response should be the highest + // priority + mPriorityUrgency = 1; + } else if (aCos & nsIClassOfService::Leader) { + // main html document normal priority + mPriorityUrgency = 2; + } else if (aCos & nsIClassOfService::Unblocked) { + mPriorityUrgency = 3; + } else if (aCos & nsIClassOfService::Follower) { + mPriorityUrgency = 4; + } else if (aCos & nsIClassOfService::Speculative) { + mPriorityUrgency = 6; + } else if (aCos & nsIClassOfService::Background) { + // background tasks can be deprioritzed to the lowest priority + mPriorityUrgency = 6; + } else if (aCos & nsIClassOfService::Tail) { + mPriorityUrgency = 6; + } else { + // all others get a lower priority than the main html document + mPriorityUrgency = 4; + } +} + +void Http3Stream::SetIncremental(bool incremental) { + mPriorityIncremental = incremental; +} + +nsresult Http3Stream::TryActivating() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + LOG(("Http3Stream::TryActivating [this=%p]", this)); + nsHttpRequestHead* head = mTransaction->RequestHead(); + + nsAutoCString authorityHeader; + nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + return rv; + } + + nsDependentCString scheme(head->IsHTTPS() ? "https" : "http"); + + nsAutoCString method; + nsAutoCString path; + head->Method(method); + head->Path(path); + +#ifdef DEBUG + nsAutoCString contentLength; + if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Length, contentLength))) { + int64_t len; + if (nsHttp::ParseInt64(contentLength.get(), nullptr, &len)) { + mRequestBodyLenExpected = len; + } + } +#endif + + return mSession->TryActivating(method, scheme, authorityHeader, path, + mFlatHttpRequestHeaders, &mStreamId, this); +} + +void Http3Stream::CurrentBrowserIdChanged(uint64_t id) { + MOZ_ASSERT(gHttpHandler->ActiveTabPriority()); + + bool previouslyFocused = (mCurrentBrowserId == mTransactionBrowserId); + mCurrentBrowserId = id; + bool nowFocused = (mCurrentBrowserId == mTransactionBrowserId); + + if (!StaticPrefs:: + network_http_http3_send_background_tabs_deprioritization() || + previouslyFocused == nowFocused) { + return; + } + + mSession->SendPriorityUpdateFrame(mStreamId, PriorityUrgency(), + PriorityIncremental()); +} + +nsresult Http3Stream::OnReadSegment(const char* buf, uint32_t count, + uint32_t* countRead) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + LOG(("Http3Stream::OnReadSegment count=%u state=%d [this=%p]", count, + mSendState, this)); + + nsresult rv = NS_OK; + + switch (mSendState) { + case PREPARING_HEADERS: { + bool done = GetHeadersString(buf, count, countRead); + + if (*countRead) { + mTotalSent += *countRead; + } + + if (!done) { + break; + } + mSendState = WAITING_TO_ACTIVATE; + } + [[fallthrough]]; + case WAITING_TO_ACTIVATE: + rv = TryActivating(); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + LOG3(("Http3Stream::OnReadSegment %p cannot activate now. queued.\n", + this)); + rv = *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + break; + } + if (NS_FAILED(rv)) { + LOG3(("Http3Stream::OnReadSegment %p cannot activate error=0x%" PRIx32 + ".", + this, static_cast<uint32_t>(rv))); + break; + } + + // Successfully activated. + mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO, + mTotalSent); + + mSendState = SENDING_BODY; + break; + case SENDING_BODY: { + rv = mSession->SendRequestBody(mStreamId, buf, count, countRead); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mSendingBlockedByFlowControlCount++; + } + + if (NS_FAILED(rv)) { + LOG3( + ("Http3Stream::OnReadSegment %p sending body returns " + "error=0x%" PRIx32 ".", + this, static_cast<uint32_t>(rv))); + break; + } + +#ifdef DEBUG + mRequestBodyLenSent += *countRead; +#endif + mTotalSent += *countRead; + mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO, + mTotalSent); + } break; + case EARLY_RESPONSE: + // We do not need to send the rest of the request, so just ignore it. + *countRead = count; +#ifdef DEBUG + mRequestBodyLenSent += count; +#endif + break; + default: + MOZ_ASSERT(false, "We are done sending this request!"); + rv = NS_ERROR_UNEXPECTED; + break; + } + + mSocketOutCondition = rv; + + return mSocketOutCondition; +} + +void Http3Stream::SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, + bool aFin, bool interim) { + MOZ_ASSERT(mRecvState == BEFORE_HEADERS || + mRecvState == READING_INTERIM_HEADERS); + mFlatResponseHeaders.AppendElements(aResponseHeaders); + mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS; + mDataReceived = true; + mFin = aFin; +} + +void Http3Stream::StopSending() { + MOZ_ASSERT((mSendState == SENDING_BODY) || (mSendState == SEND_DONE)); + if (mSendState == SENDING_BODY) { + mSendState = EARLY_RESPONSE; + } +} + +nsresult Http3Stream::OnWriteSegment(char* buf, uint32_t count, + uint32_t* countWritten) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + LOG(("Http3Stream::OnWriteSegment [this=%p, state=%d", this, mRecvState)); + nsresult rv = NS_OK; + switch (mRecvState) { + case BEFORE_HEADERS: { + *countWritten = 0; + rv = NS_BASE_STREAM_WOULD_BLOCK; + } break; + case READING_HEADERS: + case READING_INTERIM_HEADERS: { + // SetResponseHeaders should have been previously called. + MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!"); + *countWritten = (mFlatResponseHeaders.Length() > count) + ? count + : mFlatResponseHeaders.Length(); + memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten); + + mFlatResponseHeaders.RemoveElementsAt(0, *countWritten); + if (mFlatResponseHeaders.Length() == 0) { + if (mRecvState == READING_INTERIM_HEADERS) { + // neqo makes sure that fin cannot be received before the final + // headers are received. + MOZ_ASSERT(!mFin); + mRecvState = BEFORE_HEADERS; + } else { + mRecvState = mFin ? RECEIVED_FIN : READING_DATA; + } + } + + if (*countWritten == 0) { + rv = NS_BASE_STREAM_WOULD_BLOCK; + } else { + mTotalRead += *countWritten; + mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM, + mTotalRead); + } + } break; + case READING_DATA: { + rv = mSession->ReadResponseData(mStreamId, buf, count, countWritten, + &mFin); + if (NS_FAILED(rv)) { + break; + } + if (*countWritten == 0) { + if (mFin) { + mRecvState = RECV_DONE; + rv = NS_BASE_STREAM_CLOSED; + } else { + rv = NS_BASE_STREAM_WOULD_BLOCK; + } + } else { + mTotalRead += *countWritten; + mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM, + mTotalRead); + + if (mFin) { + mRecvState = RECEIVED_FIN; + } + } + } break; + case RECEIVED_FIN: + rv = NS_BASE_STREAM_CLOSED; + mRecvState = RECV_DONE; + break; + case RECV_DONE: + rv = NS_ERROR_UNEXPECTED; + } + + // Remember the error received from lower layers. A stream pipe may overwrite + // it. + // If rv == NS_OK this will reset mSocketInCondition. + mSocketInCondition = rv; + + return rv; +} + +nsresult Http3Stream::ReadSegments() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (mRecvState == RECV_DONE) { + // Don't transmit any request frames if the peer cannot respond or respone + // is already done. + LOG3( + ("Http3Stream %p ReadSegments request stream aborted due to" + " response side closure\n", + this)); + return NS_ERROR_ABORT; + } + + nsresult rv = NS_OK; + uint32_t transactionBytes; + bool again = true; + do { + transactionBytes = 0; + rv = mSocketOutCondition = NS_OK; + LOG(("Http3Stream::ReadSegments state=%d [this=%p]", mSendState, this)); + switch (mSendState) { + case WAITING_TO_ACTIVATE: { + // A transaction that had already generated its headers before it was + // queued at the session level (due to concurrency concerns) may not + // call onReadSegment off the ReadSegments() stack above. + LOG3( + ("Http3Stream %p ReadSegments forcing OnReadSegment call\n", this)); + uint32_t wasted = 0; + nsresult rv2 = OnReadSegment("", 0, &wasted); + LOG3((" OnReadSegment returned 0x%08" PRIx32, + static_cast<uint32_t>(rv2))); + if (mSendState != SENDING_BODY) { + break; + } + } + // If we are in state SENDING_BODY we can continue sending data. + [[fallthrough]]; + case PREPARING_HEADERS: + case SENDING_BODY: { + rv = mTransaction->ReadSegmentsAgain( + this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again); + } break; + default: + transactionBytes = 0; + rv = NS_OK; + break; + } + + LOG(("Http3Stream::ReadSegments rv=0x%" PRIx32 " read=%u sock-cond=%" PRIx32 + " again=%d [this=%p]", + static_cast<uint32_t>(rv), transactionBytes, + static_cast<uint32_t>(mSocketOutCondition), again, this)); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) { + rv = NS_OK; + transactionBytes = 0; + } + + if (NS_FAILED(rv)) { + // if the transaction didn't want to write any more data, then + // wait for the transaction to call ResumeSend. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + rv = NS_OK; + } + again = false; + } else if (NS_FAILED(mSocketOutCondition)) { + if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) { + rv = mSocketOutCondition; + } + again = false; + } else if (!transactionBytes) { + mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0); + mSession->CloseSendingSide(mStreamId); + mSendState = SEND_DONE; + Telemetry::Accumulate( + Telemetry::HTTP3_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_TRANS, + mSendingBlockedByFlowControlCount); + +#ifdef DEBUG + MOZ_ASSERT(mRequestBodyLenSent == mRequestBodyLenExpected); +#endif + rv = NS_OK; + again = false; + } + // write more to the socket until error or end-of-request... + } while (again && gHttpHandler->Active()); + return rv; +} + +nsresult Http3Stream::WriteSegments() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + LOG(("Http3Stream::WriteSegments [this=%p]", this)); + nsresult rv = NS_OK; + uint32_t countWrittenSingle = 0; + bool again = true; + + do { + mSocketInCondition = NS_OK; + countWrittenSingle = 0; + rv = mTransaction->WriteSegmentsAgain( + this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again); + LOG(("Http3Stream::WriteSegments rv=0x%" PRIx32 + " countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]", + static_cast<uint32_t>(rv), countWrittenSingle, + static_cast<uint32_t>(mSocketInCondition), this)); + if (mTransaction->IsDone()) { + // If a transaction has read the amount of data specified in + // Content-Length it is marked as done.The Http3Stream should be + // marked as done as well to start the process of cleanup and + // closure. + mRecvState = RECV_DONE; + } + + if (NS_FAILED(rv)) { + // if the transaction didn't want to take any more data, then + // wait for the transaction to call ResumeRecv. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + rv = NS_OK; + } + again = false; + } else if (NS_FAILED(mSocketInCondition)) { + if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) { + rv = mSocketInCondition; + } + again = false; + } + // read more from the socket until error... + } while (again && gHttpHandler->Active()); + + return rv; +} + +bool Http3Stream::Do0RTT() { + MOZ_ASSERT(mTransaction); + mAttempting0RTT = mTransaction->Do0RTT(); + return mAttempting0RTT; +} + +nsresult Http3Stream::Finish0RTT(bool aRestart) { + MOZ_ASSERT(mTransaction); + mAttempting0RTT = false; + nsresult rv = mTransaction->Finish0RTT(aRestart, false); + if (aRestart) { + nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); + if (trans) { + trans->Refused0RTT(); + } + + // Reset Http3Sream states as well. + mSendState = PREPARING_HEADERS; + mRecvState = BEFORE_HEADERS; + mStreamId = UINT64_MAX; + mFlatHttpRequestHeaders = ""; + mQueued = false; + mDataReceived = false; + mResetRecv = false; + mFlatResponseHeaders.TruncateLength(0); + mTotalSent = 0; + mTotalRead = 0; + mFin = false; + mSendingBlockedByFlowControlCount = 0; + mSocketInCondition = NS_ERROR_NOT_INITIALIZED; + mSocketOutCondition = NS_ERROR_NOT_INITIALIZED; + } + + return rv; +} + +uint8_t Http3Stream::PriorityUrgency() { + if (!StaticPrefs::network_http_http3_priority()) { + // send default priority which is equivalent to sending no priority + return 3; + } + + if (StaticPrefs::network_http_http3_send_background_tabs_deprioritization() && + mCurrentBrowserId != mTransactionBrowserId) { + // Low priority + return 6; + } + return mPriorityUrgency; +} + +bool Http3Stream::PriorityIncremental() { + if (!StaticPrefs::network_http_http3_priority()) { + // send default priority which is equivalent to sending no priority + return false; + } + return mPriorityIncremental; +} + +} // namespace net +} // namespace mozilla |