diff options
Diffstat (limited to 'netwerk/protocol/http/Http3Stream.cpp')
-rw-r--r-- | netwerk/protocol/http/Http3Stream.cpp | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp new file mode 100644 index 0000000000..5451eb7c3b --- /dev/null +++ b/netwerk/protocol/http/Http3Stream.cpp @@ -0,0 +1,489 @@ +/* -*- 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 "nsISocketTransport.h" +#include "nsSocketTransportService2.h" + +#include <stdio.h> + +namespace mozilla { +namespace net { + +Http3Stream::Http3Stream(nsAHttpTransaction* httpTransaction, + Http3Session* session) + : mSendState(PREPARING_HEADERS), + mRecvState(BEFORE_HEADERS), + mStreamId(UINT64_MAX), + mSession(session), + mTransaction(httpTransaction), + mQueued(false), + mDataReceived(false), + mResetRecv(false), + mRequestBodyLenRemaining(0), + mSocketTransport(session->SocketTransport()), + mTotalSent(0), + mTotalRead(0), + mFin(false), + mSendingBlockedByFlowControlCount(0) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + LOG3(("Http3Stream::Http3Stream [this=%p]", this)); +} + +void Http3Stream::Close(nsresult aResult) { + mRecvState = RECV_DONE; + mTransaction->Close(aResult); +} + +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 = %u", + this, mFlatHttpRequestHeaders.Length())); + *countUsed = avail; + return false; + } + + uint32_t oldLen = mFlatHttpRequestHeaders.Length(); + mFlatHttpRequestHeaders.SetLength(endHeader + 2); + *countUsed = avail - (oldLen - endHeader) + 4; + + FindRequestContentLength(); + return true; +} + +void Http3Stream::FindRequestContentLength() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + // Look for Content-Length header to find out if we have request body and + // how long it is. + int32_t contentLengthStart = mFlatHttpRequestHeaders.Find("Content-Length:"); + if (contentLengthStart == -1) { + // There is no content-Length. + return; + } + + // We have Content-Length header, find the end of it. + int32_t crlfIndex = + mFlatHttpRequestHeaders.Find("\r\n", false, contentLengthStart); + if (crlfIndex == -1) { + MOZ_ASSERT(false, "We must have \\r\\n at the end of the headers string."); + return; + } + + // Find the beginning. + int32_t valueIndex = + mFlatHttpRequestHeaders.Find(":", false, contentLengthStart) + 1; + if (valueIndex > crlfIndex) { + // Content-Length headers is empty. + MOZ_ASSERT(false, "Content-Length must have a value."); + return; + } + + const char* beginBuffer = mFlatHttpRequestHeaders.BeginReading(); + while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') { + ++valueIndex; + } + + nsDependentCSubstring value = + Substring(beginBuffer + valueIndex, beginBuffer + crlfIndex); + + int64_t len; + nsCString tmp(value); + if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) { + mRequestBodyLenRemaining = len; + } +} + +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); + + return mSession->TryActivating(method, scheme, authorityHeader, path, + mFlatHttpRequestHeaders, &mStreamId, this); +} + +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(mSocketTransport, + NS_NET_STATUS_SENDING_TO, mTotalSent); + + if (mRequestBodyLenRemaining) { + mSendState = SENDING_BODY; + } else { + mTransaction->OnTransportStatus(mSocketTransport, + NS_NET_STATUS_WAITING_FOR, 0); + mSession->CloseSendingSide(mStreamId); + mSendState = SEND_DONE; + } + break; + case SENDING_BODY: { + rv = mSession->SendRequestBody(mStreamId, buf, count, countRead); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mSendingBlockedByFlowControlCount++; + } + MOZ_ASSERT(mRequestBodyLenRemaining >= *countRead, + "We cannot send more that than we promised."); + if (mRequestBodyLenRemaining < *countRead) { + rv = NS_ERROR_UNEXPECTED; + } + if (NS_FAILED(rv)) { + LOG3( + ("Http3Stream::OnReadSegment %p sending body returns " + "error=0x%" PRIx32 ".", + this, static_cast<uint32_t>(rv))); + break; + } + + mRequestBodyLenRemaining -= *countRead; + if (!mRequestBodyLenRemaining) { + mTransaction->OnTransportStatus(mSocketTransport, + NS_NET_STATUS_WAITING_FOR, 0); + mSession->CloseSendingSide(mStreamId); + mSendState = SEND_DONE; + Telemetry::Accumulate( + Telemetry::HTTP3_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_TRANS, + mSendingBlockedByFlowControlCount); + } + } break; + case EARLY_RESPONSE: + // We do not need to send the rest of the request, so just ignore it. + *countRead = count; + mRequestBodyLenRemaining -= count; + if (!mRequestBodyLenRemaining) { + mTransaction->OnTransportStatus(mSocketTransport, + NS_NET_STATUS_WAITING_FOR, 0); + mSendState = SEND_DONE; + } + break; + default: + MOZ_ASSERT(false, "We are done sending this request!"); + break; + } + + mSocketOutCondition = rv; + + return mSocketOutCondition; +} + +void Http3Stream::SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, + bool aFin) { + MOZ_ASSERT(mRecvState == BEFORE_HEADERS); + MOZ_ASSERT(mFlatResponseHeaders.IsEmpty(), + "Cannot set response headers more than once"); + mFlatResponseHeaders = std::move(aResponseHeaders); + mRecvState = 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: { + // 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) { + mRecvState = mFin ? RECEIVED_FIN : READING_DATA; + } + + if (*countWritten == 0) { + rv = NS_BASE_STREAM_WOULD_BLOCK; + } else { + mTotalRead += *countWritten; + mTransaction->OnTransportStatus( + mSocketTransport, 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( + mSocketTransport, 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(nsAHttpSegmentReader* reader) { + 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) { + 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(nsAHttpSegmentWriter* writer, + uint32_t count, uint32_t* countWritten) { + 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; + rv = mTransaction->WriteSegmentsAgain(this, count, &countWrittenSingle, + &again); + *countWritten += countWrittenSingle; + 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); + mRequestBodyLenRemaining = 0; + mTotalSent = 0; + mTotalRead = 0; + mFin = false; + mSendingBlockedByFlowControlCount = 0; + mSocketInCondition = NS_ERROR_NOT_INITIALIZED; + mSocketOutCondition = NS_ERROR_NOT_INITIALIZED; + } + + return rv; +} + +} // namespace net +} // namespace mozilla |