/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ #include #include "TestCommon.h" #include "gtest/gtest.h" #include "Http2WebTransportSession.h" #include "Http2WebTransportStream.h" #include "nsString.h" #include "nsTArray.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/Queue.h" #include "mozilla/net/NeqoHttp3Conn.h" #include "Capsule.h" #include "CapsuleEncoder.h" #include "CapsuleParser.h" #include "nsIWebTransport.h" #include "nsStreamUtils.h" #include "nsThreadUtils.h" using namespace mozilla; using namespace mozilla::net; class MockWebTransportClient : public CapsuleIOHandler { public: NS_INLINE_DECL_REFCOUNTING(MockWebTransportClient, override) explicit MockWebTransportClient(Http2WebTransportInitialSettings aSettings) : mSession(new Http2WebTransportSessionImpl(this, aSettings)), mParser(MakeUnique(mSession)) {} Http2WebTransportSessionImpl* Session() { return mSession; } void HasCapsuleToSend() override { mSession->PrepareCapsulesToSend(mOutCapsules); } void SetSentFin() override { mSetSentFinCalled = true; } void StartReading() override { mStartReadingCalled = true; } void OnCapsuleParseFailure(nsresult aError) override { mOnParseFailureCalled = true; } void ProcessInputCapsules( mozilla::Queue>&& aCapsules) { while (!aCapsules.IsEmpty()) { UniquePtr capsule = aCapsules.Pop(); auto buffer = capsule->GetBuffer(); mParser->ProcessCapsuleData(buffer.Elements(), buffer.Length()); } } void ProcessOutput() { mSession->PrepareCapsulesToSend(mOutCapsules); mozilla::Queue> queue(std::move(mOutCapsules)); while (!queue.IsEmpty()) { UniquePtr encoder = queue.Pop(); auto metadata = encoder->GetStreamMetadata(); if (metadata) { mSession->OnStreamDataSent(StreamId(metadata->mID), metadata->mDataSize); } mOutCapsules.Push(std::move(encoder)); } } mozilla::Queue> GetOutCapsules() { return std::move(mOutCapsules); } void Done() { mParser = nullptr; mSession->Close(NS_OK); mSession = nullptr; } // State tracking bool mSetSentFinCalled = false; bool mStartReadingCalled = false; bool mOnParseFailureCalled = false; private: ~MockWebTransportClient() = default; RefPtr mSession; UniquePtr mParser; mozilla::Queue> mOutCapsules; }; class MockWebTransportServer : public CapsuleParser::Listener { public: NS_INLINE_DECL_REFCOUNTING(MockWebTransportServer, override) explicit MockWebTransportServer() : mParser(MakeUnique(this)) {} bool OnCapsule(Capsule&& aCapsule) override { mReceivedCapsules.AppendElement(std::move(aCapsule)); return true; } void OnCapsuleParseFailure(nsresult aError) override { MOZ_RELEASE_ASSERT(false); } nsTArray GetReceivedCapsules() { return std::move(mReceivedCapsules); } void ProcessInputCapsules( mozilla::Queue>&& aCapsules) { while (!aCapsules.IsEmpty()) { UniquePtr capsule = aCapsules.Pop(); auto buffer = capsule->GetBuffer(); mParser->ProcessCapsuleData(buffer.Elements(), buffer.Length()); } } void SendWebTransportMaxStreamsCapsule(uint64_t aLimit, bool aBidi) { Capsule capsule = Capsule::WebTransportMaxStreams(aLimit, aBidi); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendWebTransportStreamDataCapsule(uint64_t aID, bool aFin, nsTArray&& aData) { Capsule capsule = Capsule::WebTransportStreamData(aID, aFin, std::move(aData)); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendWebTransportMaxStreamDataCapsule(uint64_t aLimit, uint64_t aID) { Capsule capsule = Capsule::WebTransportMaxStreamData(aLimit, aID); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendWebTransportMaxDataCapsule(uint64_t aLimit) { Capsule capsule = Capsule::WebTransportMaxData(aLimit); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendWebTransportStopSendingCapsule(uint64_t aError, uint64_t aID) { Capsule capsule = Capsule::WebTransportStopSending(aError, aID); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendWebTransportResetStreamCapsule(uint64_t aError, uint64_t aSize, uint64_t aID) { Capsule capsule = Capsule::WebTransportResetStream(aError, aSize, aID); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } void SendDatagramCapsule(nsTArray&& aPayload) { Capsule capsule = Capsule::WebTransportDatagram(std::move(aPayload)); UniquePtr encoder = MakeUnique(); encoder->EncodeCapsule(capsule); mOutCapsules.Push(std::move(encoder)); } mozilla::Queue> GetOutCapsules() { return std::move(mOutCapsules); } void Done() { mParser = nullptr; } private: ~MockWebTransportServer() = default; UniquePtr mParser; nsTArray mReceivedCapsules; mozilla::Queue> mOutCapsules; }; // TODO: will be used when testing incoming streams. class MockWebTransportSessionEventListener : public WebTransportSessionEventListener, public WebTransportSessionEventListenerInternal { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENERINTERNAL MockWebTransportSessionEventListener() {} nsTArray> TakeIncomingStreams() { return std::move(mIncomingStreams); } nsTArray mReceivedDatagrams; Maybe> TakeStopSending() { return std::move(mStopSending); } Maybe> TakeReset() { return std::move(mReset); } private: virtual ~MockWebTransportSessionEventListener() = default; nsTArray> mIncomingStreams; Maybe> mStopSending; Maybe> mReset; }; NS_IMPL_ISUPPORTS(MockWebTransportSessionEventListener, WebTransportSessionEventListener, WebTransportSessionEventListenerInternal) NS_IMETHODIMP MockWebTransportSessionEventListener::OnSessionReadyInternal( WebTransportSessionBase* aSession) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnIncomingStreamAvailableInternal( WebTransportStreamBase* aStream) { mIncomingStreams.AppendElement(RefPtr{aStream}); return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnIncomingBidirectionalStreamAvailable( nsIWebTransportBidirectionalStream* aStream) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnIncomingUnidirectionalStreamAvailable( nsIWebTransportReceiveStream* aStream) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnSessionReady(uint64_t ready) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnSessionClosed( bool aCleanly, uint32_t aStatus, const nsACString& aReason) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnDatagramReceivedInternal( nsTArray&& aData) { mReceivedDatagrams = std::move(aData); return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnDatagramReceived( const nsTArray& aData) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnMaxDatagramSize( uint64_t aSize) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnOutgoingDatagramOutCome( uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) { return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnStopSending( uint64_t aStreamId, nsresult aError) { mStopSending = Some(std::pair(aStreamId, aError)); return NS_OK; } NS_IMETHODIMP MockWebTransportSessionEventListener::OnResetReceived( uint64_t aStreamId, nsresult aError) { mReset = Some(std::pair(aStreamId, aError)); return NS_OK; } static void ServerProcessCapsules(MockWebTransportServer* aServer, MockWebTransportClient* aClient) { aClient->ProcessOutput(); mozilla::Queue> outCapsules = aClient->GetOutCapsules(); aServer->ProcessInputCapsules(std::move(outCapsules)); } static void ClientProcessCapsules(MockWebTransportServer* aServer, MockWebTransportClient* aClient) { mozilla::Queue> outCapsules = aServer->GetOutCapsules(); aClient->ProcessInputCapsules(std::move(outCapsules)); } TEST(TestHttp2WebTransport, CloseSessionCapsule) { RefPtr client = new MockWebTransportClient(Http2WebTransportInitialSettings()); RefPtr server = new MockWebTransportServer(); nsCString reason("test"); client->Session()->CloseSession(42, "test"_ns); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); CloseWebTransportSessionCapsule& parsedCapsule = received[0].GetCloseWebTransportSessionCapsule(); ASSERT_EQ(parsedCapsule.mStatus, 42u); ASSERT_EQ(parsedCapsule.mReason, reason); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, CreateOutgoingStream) { RefPtr client = new MockWebTransportClient(Http2WebTransportInitialSettings()); RefPtr server = new MockWebTransportServer(); RefPtr bidiStream; auto callback = [&](Result, nsresult>&& aResult) { if (aResult.isErr()) { return; } bidiStream = aResult.unwrap(); }; client->Session()->CreateOutgoingBidirectionalStream(std::move(callback)); ASSERT_TRUE(bidiStream == nullptr); ServerProcessCapsules(server, client); // Creating a stream is blocked, we should see a // WebTransportStreamsBlockedCapsule from the client. nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamsBlockedCapsule& streamsBlocked = received[0].GetWebTransportStreamsBlockedCapsule(); ASSERT_EQ(streamsBlocked.mLimit, 0u); ASSERT_EQ(streamsBlocked.mBidi, true); server->SendWebTransportMaxStreamsCapsule(1, true); ClientProcessCapsules(server, client); ASSERT_TRUE(bidiStream != nullptr); RefPtr unidiStream; auto callback1 = [&](Result, nsresult>&& aResult) { if (aResult.isErr()) { return; } unidiStream = aResult.unwrap(); }; client->Session()->CreateOutgoingUnidirectionalStream(std::move(callback1)); ASSERT_TRUE(unidiStream == nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamsBlockedCapsule& streamsBlocked1 = received[0].GetWebTransportStreamsBlockedCapsule(); ASSERT_EQ(streamsBlocked1.mLimit, 0u); ASSERT_EQ(streamsBlocked1.mBidi, false); server->SendWebTransportMaxStreamsCapsule(1, false); ClientProcessCapsules(server, client); ASSERT_TRUE(unidiStream != nullptr); client->Done(); server->Done(); } static void CreateTestData(uint32_t aNumBytes, nsTArray& aDataOut) { static constexpr const char kSampleText[] = "{\"type\":\"message\",\"id\":42,\"payload\":\"The quick brown fox jumps " "over the lazy dog.\"}"; static constexpr uint32_t kSampleTextLen = sizeof(kSampleText) - 1; aDataOut.SetCapacity(aNumBytes); while (aNumBytes > 0) { uint32_t chunkSize = std::min(kSampleTextLen, aNumBytes); aDataOut.AppendElements(reinterpret_cast(kSampleText), chunkSize); aNumBytes -= chunkSize; } } static void ValidateData(nsTArray& aInput, nsTArray& aExpectedData) { ASSERT_EQ(aExpectedData.Length(), aInput.Length()); for (size_t i = 0; i < aExpectedData.Length(); i++) { ASSERT_EQ(aExpectedData[i], aInput[i]); } } static void ValidateData(nsIInputStream* aStream, nsTArray& aExpectedData) { nsTArray outputData; nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData); ASSERT_NS_SUCCEEDED(rv); ValidateData(outputData, aExpectedData); } static std::pair, nsCOMPtr> CreateStreamAndSendData(WebTransportStreamBase* aStream, const nsTArray& aData) { nsCOMPtr writer; nsCOMPtr reader; aStream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); uint32_t numWritten = 0; Unused << writer->Write((const char*)aData.Elements(), aData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); return std::make_pair(writer, reader); } static void ValidateStreamCapsule(MockWebTransportServer* aServer, nsTArray& aExpectedData, bool aExpectBidi) { nsTArray received = aServer->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataCapsule& streamData = received[0].GetWebTransportStreamDataCapsule(); StreamId id(streamData.mID); ASSERT_TRUE(id.IsClientInitiated()); ASSERT_EQ(id.IsBiDi(), aExpectBidi); ValidateData(streamData.mData, aExpectedData); } static already_AddRefed CreateOutgoingStream( MockWebTransportClient* aClient, bool aBidi = true) { RefPtr stream; auto callback = [&](Result, nsresult>&& aResult) { if (aResult.isErr()) { return; } stream = aResult.unwrap(); MOZ_RELEASE_ASSERT(stream); }; if (aBidi) { aClient->Session()->CreateOutgoingBidirectionalStream(std::move(callback)); } else { aClient->Session()->CreateOutgoingUnidirectionalStream(std::move(callback)); } return stream.forget(); } TEST(TestHttp2WebTransport, OutgoingUniStream) { Http2WebTransportInitialSettings settings; settings.mInitialMaxData = 1024; settings.mInitialMaxStreamsUni = 1; settings.mInitialMaxStreamDataUni = 512; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); // Try to create bidi stream (should fail and trigger streams-blocked capsule) RefPtr bidiStream; client->Session()->CreateOutgoingBidirectionalStream([&](auto&& aResult) { if (aResult.isOk()) { bidiStream = aResult.unwrap(); } }); ASSERT_TRUE(bidiStream == nullptr); ServerProcessCapsules(server, client); { nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); auto& capsule = received[0].GetWebTransportStreamsBlockedCapsule(); ASSERT_EQ(capsule.mLimit, 0u); ASSERT_TRUE(capsule.mBidi); } // Create unidirectional stream and send data RefPtr unidiStream = CreateOutgoingStream(client, false); ASSERT_TRUE(unidiStream != nullptr); nsTArray inputData; CreateTestData(512, inputData); CreateStreamAndSendData(unidiStream, inputData); ServerProcessCapsules(server, client); ValidateStreamCapsule(server, inputData, /* aExpectBidi = */ false); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, OutgoingBidiStream) { Http2WebTransportInitialSettings settings; settings.mInitialMaxData = 1024; settings.mInitialMaxStreamsUni = 1; settings.mInitialMaxStreamsBidi = 1; settings.mInitialMaxStreamDataBidi = 512; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr bidiStream = CreateOutgoingStream(client); ASSERT_TRUE(bidiStream != nullptr); nsTArray inputData; CreateTestData(512, inputData); CreateStreamAndSendData(bidiStream, inputData); ServerProcessCapsules(server, client); ValidateStreamCapsule(server, inputData, /* aExpectBidi = */ true); // Echo back from server nsTArray echo; echo.AppendElements(inputData.Elements(), inputData.Length()); StreamId id = StreamId::From(0); server->SendWebTransportStreamDataCapsule(id, false, std::move(echo)); ClientProcessCapsules(server, client); nsCOMPtr reader; nsCOMPtr writer; bidiStream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); uint64_t available = 0; Unused << reader->Available(&available); EXPECT_EQ(available, inputData.Length()); ValidateData(reader, inputData); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, IncomingBidiStream) { Http2WebTransportInitialSettings settings; settings.mInitialLocalMaxStreamsBidi = 1; settings.mInitialLocalMaxStreamDataBidi = 512; RefPtr client = new MockWebTransportClient(settings); RefPtr listener = new MockWebTransportSessionEventListener(); client->Session()->SetWebTransportSessionEventListener(listener); RefPtr server = new MockWebTransportServer(); nsTArray inputData; CreateTestData(512, inputData); nsTArray cloned(inputData.Clone()); server->SendWebTransportStreamDataCapsule(1, false, std::move(cloned)); ClientProcessCapsules(server, client); nsTArray> streams = listener->TakeIncomingStreams(); ASSERT_EQ(streams.Length(), 1u); nsCOMPtr writer; nsCOMPtr reader; RefPtr stream = streams[0]; stream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); ValidateData(reader, inputData); cloned = inputData.Clone(); server->SendWebTransportStreamDataCapsule(5, false, std::move(cloned)); ClientProcessCapsules(server, client); streams = listener->TakeIncomingStreams(); ASSERT_EQ(streams.Length(), 0u); client->Session()->OnStreamClosed( static_cast(stream.get())); ServerProcessCapsules(server, client); cloned = inputData.Clone(); server->SendWebTransportStreamDataCapsule(5, false, std::move(cloned)); ClientProcessCapsules(server, client); streams = listener->TakeIncomingStreams(); ASSERT_EQ(streams.Length(), 1u); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamDataSenderFlowControl) { Http2WebTransportInitialSettings settings; settings.mInitialMaxData = 1024; settings.mInitialMaxStreamsBidi = 1; settings.mInitialMaxStreamDataBidi = 100; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr bidiStream = CreateOutgoingStream(client); ASSERT_TRUE(bidiStream != nullptr); nsCOMPtr writer; nsCOMPtr reader; bidiStream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); nsTArray inputData; CreateTestData(100, inputData); uint32_t numWritten = 0; Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataCapsule& streamData = received[0].GetWebTransportStreamDataCapsule(); StreamId id(streamData.mID); ASSERT_TRUE(id.IsClientInitiated()); ASSERT_TRUE(id.IsBiDi()); ValidateData(streamData.mData, inputData); numWritten = 0; Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataBlockedCapsule& blocked = received[0].GetWebTransportStreamDataBlockedCapsule(); StreamId receivedId(blocked.mID); ASSERT_EQ(id, receivedId); ASSERT_EQ(blocked.mLimit, 100u); server->SendWebTransportMaxStreamDataCapsule(300, id); ClientProcessCapsules(server, client); Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataCapsule& streamData1 = received[0].GetWebTransportStreamDataCapsule(); ASSERT_EQ(streamData1.mData.Length(), 200u); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamDataSenderFlowControlMaxData) { Http2WebTransportInitialSettings settings; settings.mInitialMaxData = 100; settings.mInitialMaxStreamsBidi = 1; settings.mInitialMaxStreamDataBidi = 100; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr bidiStream = CreateOutgoingStream(client); ASSERT_TRUE(bidiStream != nullptr); nsCOMPtr writer; nsCOMPtr reader; bidiStream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); nsTArray inputData; CreateTestData(100, inputData); uint32_t numWritten = 0; Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataCapsule& streamData = received[0].GetWebTransportStreamDataCapsule(); StreamId id(streamData.mID); ASSERT_TRUE(id.IsClientInitiated()); ASSERT_TRUE(id.IsBiDi()); ValidateData(streamData.mData, inputData); numWritten = 0; Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 2u); WebTransportDataBlockedCapsule& sessionDataBlocked = received[0].GetWebTransportDataBlockedCapsule(); ASSERT_EQ(sessionDataBlocked.mLimit, 100u); WebTransportStreamDataBlockedCapsule& blocked = received[1].GetWebTransportStreamDataBlockedCapsule(); StreamId receivedId(blocked.mID); ASSERT_EQ(id, receivedId); ASSERT_EQ(blocked.mLimit, 100u); server->SendWebTransportMaxStreamDataCapsule(500, id); ClientProcessCapsules(server, client); Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 0u); server->SendWebTransportMaxDataCapsule(1024); ClientProcessCapsules(server, client); Unused << writer->Write((const char*)inputData.Elements(), inputData.Length(), &numWritten); NS_ProcessPendingEvents(nullptr); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStreamDataCapsule& streamData1 = received[0].GetWebTransportStreamDataCapsule(); ASSERT_EQ(streamData1.mData.Length(), 300u); client->Done(); server->Done(); } static void CheckFc(ReceiverFlowControlBase& aFc, uint64_t aConsumed, uint64_t aRetired) { MOZ_RELEASE_ASSERT(aFc.Consumed() == aConsumed); MOZ_RELEASE_ASSERT(aFc.Retired() == aRetired); } TEST(TestHttp2WebTransport, ReceiverFlowControl) { const uint32_t FC_SIZE = 1024; Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsBidi = 2; settings.mInitialLocalMaxStreamDataBidi = FC_SIZE * 3 / 4; settings.mInitialLocalMaxData = FC_SIZE; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr s1 = CreateOutgoingStream(client); ASSERT_TRUE(s1 != nullptr); RefPtr s2 = CreateOutgoingStream(client); ASSERT_TRUE(s2 != nullptr); CheckFc(client->Session()->ReceiverFc(), 0, 0); CheckFc(*s1->ReceiverFc(), 0, 0); CheckFc(*s2->ReceiverFc(), 0, 0); nsTArray inputData; CreateTestData(FC_SIZE / 4, inputData); StreamId id = StreamId::From(0); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); CreateTestData(FC_SIZE / 4, inputData); StreamId id1 = StreamId::From(4); server->SendWebTransportStreamDataCapsule(id1, false, std::move(inputData)); ClientProcessCapsules(server, client); CheckFc(client->Session()->ReceiverFc(), FC_SIZE / 2, FC_SIZE / 2); CheckFc(*s1->ReceiverFc(), FC_SIZE / 4, FC_SIZE / 4); CheckFc(*s2->ReceiverFc(), FC_SIZE / 4, FC_SIZE / 4); CreateTestData(FC_SIZE / 4, inputData); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); ClientProcessCapsules(server, client); CheckFc(client->Session()->ReceiverFc(), FC_SIZE * 3 / 4, FC_SIZE * 3 / 4); CheckFc(*s1->ReceiverFc(), FC_SIZE / 2, FC_SIZE / 2); CheckFc(*s2->ReceiverFc(), FC_SIZE / 4, FC_SIZE / 4); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, ReceiverFlowControl1) { const uint32_t FC_SIZE = 1024; Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsBidi = 1; settings.mInitialLocalMaxStreamDataBidi = FC_SIZE / 2; settings.mInitialLocalMaxData = FC_SIZE; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr bidiStream = CreateOutgoingStream(client); ASSERT_TRUE(bidiStream != nullptr); nsCOMPtr writer; nsCOMPtr reader; bidiStream->GetWriterAndReader(getter_AddRefs(writer), getter_AddRefs(reader)); nsTArray inputData; CreateTestData(FC_SIZE / 4, inputData); StreamId id = StreamId::From(0); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); ClientProcessCapsules(server, client); uint64_t available = 0; Unused << reader->Available(&available); EXPECT_EQ(available, FC_SIZE / 4); nsTArray outputData; nsresult rv = NS_ConsumeStream(reader, UINT32_MAX, outputData); ASSERT_NS_SUCCEEDED(rv); CheckFc(client->Session()->ReceiverFc(), FC_SIZE / 4, FC_SIZE / 4); CheckFc(*bidiStream->ReceiverFc(), FC_SIZE / 4, FC_SIZE / 4); CreateTestData(1, inputData); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); ClientProcessCapsules(server, client); CheckFc(client->Session()->ReceiverFc(), FC_SIZE / 4 + 1, FC_SIZE / 4 + 1); CheckFc(*bidiStream->ReceiverFc(), FC_SIZE / 4 + 1, FC_SIZE / 4 + 1); available = 0; Unused << reader->Available(&available); EXPECT_EQ(available, 1u); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportMaxStreamDataCapsule& capsule = received[0].GetWebTransportMaxStreamDataCapsule(); ASSERT_EQ(capsule.mID, 0u); ASSERT_EQ(capsule.mLimit, FC_SIZE * 3 / 4 + 1); CreateTestData(FC_SIZE / 4 - 1, inputData); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); ClientProcessCapsules(server, client); CheckFc(client->Session()->ReceiverFc(), FC_SIZE / 2, FC_SIZE / 2); CheckFc(*bidiStream->ReceiverFc(), FC_SIZE / 2, FC_SIZE / 2); CreateTestData(1, inputData); server->SendWebTransportStreamDataCapsule(id, false, std::move(inputData)); ClientProcessCapsules(server, client); CheckFc(client->Session()->ReceiverFc(), FC_SIZE / 2 + 1, FC_SIZE / 2 + 1); CheckFc(*bidiStream->ReceiverFc(), FC_SIZE / 2 + 1, FC_SIZE / 2 + 1); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportMaxDataCapsule& maxData = received[0].GetWebTransportMaxDataCapsule(); ASSERT_EQ(maxData.mMaxDataSize, FC_SIZE * 3 / 2 + 1); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamStopSending) { Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsUni = 1; RefPtr client = new MockWebTransportClient(settings); RefPtr server = new MockWebTransportServer(); RefPtr uniStream = CreateOutgoingStream(client, false); ASSERT_TRUE(uniStream != nullptr); uniStream->SendStopSending(0); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportStopSendingCapsule& stopSending = received[0].GetWebTransportStopSendingCapsule(); ASSERT_EQ(stopSending.mID, uniStream->WebTransportStreamId()); ASSERT_EQ(stopSending.mErrorCode, 0u); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamOnStopSending) { Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsUni = 1; RefPtr client = new MockWebTransportClient(settings); RefPtr listener = new MockWebTransportSessionEventListener(); client->Session()->SetWebTransportSessionEventListener(listener); RefPtr server = new MockWebTransportServer(); RefPtr uniStream = CreateOutgoingStream(client, false); ASSERT_TRUE(uniStream != nullptr); nsTArray inputData; CreateTestData(512, inputData); CreateStreamAndSendData(uniStream, inputData); ServerProcessCapsules(server, client); server->SendWebTransportStopSendingCapsule(0, uniStream->WebTransportStreamId()); ClientProcessCapsules(server, client); auto stopSending = listener->TakeStopSending(); ASSERT_TRUE(stopSending); ASSERT_EQ(stopSending->first, uniStream->WebTransportStreamId()); ASSERT_EQ(stopSending->second, NS_ERROR_WEBTRANSPORT_CODE_BASE); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamReset) { const uint32_t TOTAL_SIZE = 1024; Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsBidi = 1; settings.mInitialMaxStreamDataBidi = TOTAL_SIZE; settings.mInitialMaxData = TOTAL_SIZE; RefPtr client = new MockWebTransportClient(settings); RefPtr listener = new MockWebTransportSessionEventListener(); client->Session()->SetWebTransportSessionEventListener(listener); RefPtr server = new MockWebTransportServer(); RefPtr stream = CreateOutgoingStream(client); ASSERT_TRUE(stream != nullptr); nsTArray inputData; CreateTestData(TOTAL_SIZE / 4, inputData); CreateStreamAndSendData(stream, inputData); ServerProcessCapsules(server, client); inputData.Clear(); CreateTestData(TOTAL_SIZE / 4, inputData); CreateStreamAndSendData(stream, inputData); ServerProcessCapsules(server, client); nsTArray received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 2u); stream->Reset(0); ServerProcessCapsules(server, client); received = server->GetReceivedCapsules(); ASSERT_EQ(received.Length(), 1u); WebTransportResetStreamCapsule& reset = received[0].GetWebTransportResetStreamCapsule(); ASSERT_EQ(reset.mID, stream->WebTransportStreamId()); ASSERT_EQ(reset.mErrorCode, 0u); ASSERT_EQ(reset.mReliableSize, TOTAL_SIZE / 2); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, StreamResetReliableSize) { const uint32_t TOTAL_SIZE = 1024; Http2WebTransportInitialSettings settings; settings.mInitialMaxStreamsBidi = 1; settings.mInitialMaxStreamDataBidi = TOTAL_SIZE; settings.mInitialMaxData = TOTAL_SIZE; RefPtr client = new MockWebTransportClient(settings); RefPtr listener = new MockWebTransportSessionEventListener(); client->Session()->SetWebTransportSessionEventListener(listener); RefPtr server = new MockWebTransportServer(); RefPtr stream = CreateOutgoingStream(client); ASSERT_TRUE(stream != nullptr); nsTArray inputData; CreateTestData(TOTAL_SIZE / 4, inputData); auto streams = CreateStreamAndSendData(stream, inputData); ServerProcessCapsules(server, client); server->SendWebTransportStreamDataCapsule(stream->WebTransportStreamId(), false, std::move(inputData)); CreateTestData(TOTAL_SIZE / 4, inputData); server->SendWebTransportStreamDataCapsule(stream->WebTransportStreamId(), false, std::move(inputData)); server->SendWebTransportResetStreamCapsule(0, TOTAL_SIZE / 2, stream->WebTransportStreamId()); ClientProcessCapsules(server, client); auto reset = listener->TakeReset(); ASSERT_TRUE(reset); ASSERT_EQ(reset->first, stream->WebTransportStreamId()); ASSERT_EQ(reset->second, NS_ERROR_WEBTRANSPORT_CODE_BASE); client->Done(); server->Done(); } TEST(TestHttp2WebTransport, SendAndReceiveDatagram) { RefPtr client = new MockWebTransportClient(Http2WebTransportInitialSettings()); RefPtr server = new MockWebTransportServer(); RefPtr listener = new MockWebTransportSessionEventListener(); client->Session()->SetWebTransportSessionEventListener(listener); nsTArray mockData, expectedData; CreateTestData(512, mockData); expectedData.AppendElements(mockData); // Send datagram from client to server client->Session()->SendDatagram(std::move(mockData), 1); ServerProcessCapsules(server, client); // Verify the server received the correct datagram capsule nsTArray received = server->GetReceivedCapsules(); WebTransportDatagramCapsule& parsedCapsule = received[0].GetWebTransportDatagramCapsule(); ValidateData(parsedCapsule.mPayload, expectedData); // Echo and verify the received datagram payload back to the client server->SendDatagramCapsule(std::move(parsedCapsule.mPayload)); ClientProcessCapsules(server, client); ValidateData(listener->mReceivedDatagrams, expectedData); client->Done(); server->Done(); }