Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <utility>
#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<CapsuleParser>(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<UniquePtr<CapsuleEncoder>>&& aCapsules) {
while (!aCapsules.IsEmpty()) {
UniquePtr<CapsuleEncoder> capsule = aCapsules.Pop();
auto buffer = capsule->GetBuffer();
mParser->ProcessCapsuleData(buffer.Elements(), buffer.Length());
}
}
void ProcessOutput() {
mSession->PrepareCapsulesToSend(mOutCapsules);
mozilla::Queue<UniquePtr<CapsuleEncoder>> queue(std::move(mOutCapsules));
while (!queue.IsEmpty()) {
UniquePtr<CapsuleEncoder> encoder = queue.Pop();
auto metadata = encoder->GetStreamMetadata();
if (metadata) {
mSession->OnStreamDataSent(StreamId(metadata->mID),
metadata->mDataSize);
}
mOutCapsules.Push(std::move(encoder));
}
}
mozilla::Queue<UniquePtr<CapsuleEncoder>> 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<Http2WebTransportSessionImpl> mSession;
UniquePtr<CapsuleParser> mParser;
mozilla::Queue<UniquePtr<CapsuleEncoder>> mOutCapsules;
};
class MockWebTransportServer : public CapsuleParser::Listener {
public:
NS_INLINE_DECL_REFCOUNTING(MockWebTransportServer, override)
explicit MockWebTransportServer()
: mParser(MakeUnique<CapsuleParser>(this)) {}
bool OnCapsule(Capsule&& aCapsule) override {
mReceivedCapsules.AppendElement(std::move(aCapsule));
return true;
}
void OnCapsuleParseFailure(nsresult aError) override {
MOZ_RELEASE_ASSERT(false);
}
nsTArray<Capsule> GetReceivedCapsules() {
return std::move(mReceivedCapsules);
}
void ProcessInputCapsules(
mozilla::Queue<UniquePtr<CapsuleEncoder>>&& aCapsules) {
while (!aCapsules.IsEmpty()) {
UniquePtr<CapsuleEncoder> 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<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
void SendWebTransportStreamDataCapsule(uint64_t aID, bool aFin,
nsTArray<uint8_t>&& aData) {
Capsule capsule =
Capsule::WebTransportStreamData(aID, aFin, std::move(aData));
UniquePtr<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
void SendWebTransportMaxStreamDataCapsule(uint64_t aLimit, uint64_t aID) {
Capsule capsule = Capsule::WebTransportMaxStreamData(aLimit, aID);
UniquePtr<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
void SendWebTransportMaxDataCapsule(uint64_t aLimit) {
Capsule capsule = Capsule::WebTransportMaxData(aLimit);
UniquePtr<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
void SendWebTransportStopSendingCapsule(uint64_t aError, uint64_t aID) {
Capsule capsule = Capsule::WebTransportStopSending(aError, aID);
UniquePtr<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
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<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
void SendDatagramCapsule(nsTArray<uint8_t>&& aPayload) {
Capsule capsule = Capsule::WebTransportDatagram(std::move(aPayload));
UniquePtr<CapsuleEncoder> encoder = MakeUnique<CapsuleEncoder>();
encoder->EncodeCapsule(capsule);
mOutCapsules.Push(std::move(encoder));
}
mozilla::Queue<UniquePtr<CapsuleEncoder>> GetOutCapsules() {
return std::move(mOutCapsules);
}
void Done() { mParser = nullptr; }
private:
~MockWebTransportServer() = default;
UniquePtr<CapsuleParser> mParser;
nsTArray<Capsule> mReceivedCapsules;
mozilla::Queue<UniquePtr<CapsuleEncoder>> 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<RefPtr<WebTransportStreamBase>> TakeIncomingStreams() {
return std::move(mIncomingStreams);
}
nsTArray<uint8_t> mReceivedDatagrams;
Maybe<std::pair<uint64_t, nsresult>> TakeStopSending() {
return std::move(mStopSending);
}
Maybe<std::pair<uint64_t, nsresult>> TakeReset() { return std::move(mReset); }
private:
virtual ~MockWebTransportSessionEventListener() = default;
nsTArray<RefPtr<WebTransportStreamBase>> mIncomingStreams;
Maybe<std::pair<uint64_t, nsresult>> mStopSending;
Maybe<std::pair<uint64_t, nsresult>> 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<uint8_t>&& aData) {
mReceivedDatagrams = std::move(aData);
return NS_OK;
}
NS_IMETHODIMP MockWebTransportSessionEventListener::OnDatagramReceived(
const nsTArray<uint8_t>& 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<uint64_t, nsresult>(aStreamId, aError));
return NS_OK;
}
NS_IMETHODIMP MockWebTransportSessionEventListener::OnResetReceived(
uint64_t aStreamId, nsresult aError) {
mReset = Some(std::pair<uint64_t, nsresult>(aStreamId, aError));
return NS_OK;
}
static void ServerProcessCapsules(MockWebTransportServer* aServer,
MockWebTransportClient* aClient) {
aClient->ProcessOutput();
mozilla::Queue<UniquePtr<CapsuleEncoder>> outCapsules =
aClient->GetOutCapsules();
aServer->ProcessInputCapsules(std::move(outCapsules));
}
static void ClientProcessCapsules(MockWebTransportServer* aServer,
MockWebTransportClient* aClient) {
mozilla::Queue<UniquePtr<CapsuleEncoder>> outCapsules =
aServer->GetOutCapsules();
aClient->ProcessInputCapsules(std::move(outCapsules));
}
TEST(TestHttp2WebTransport, CloseSessionCapsule)
{
RefPtr<MockWebTransportClient> client =
new MockWebTransportClient(Http2WebTransportInitialSettings());
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
nsCString reason("test");
client->Session()->CloseSession(42, "test"_ns);
ServerProcessCapsules(server, client);
nsTArray<Capsule> 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<MockWebTransportClient> client =
new MockWebTransportClient(Http2WebTransportInitialSettings());
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> bidiStream;
auto callback =
[&](Result<RefPtr<WebTransportStreamBase>, 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<Capsule> 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<WebTransportStreamBase> unidiStream;
auto callback1 =
[&](Result<RefPtr<WebTransportStreamBase>, 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<uint8_t>& 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<const uint8_t*>(kSampleText),
chunkSize);
aNumBytes -= chunkSize;
}
}
static void ValidateData(nsTArray<uint8_t>& aInput,
nsTArray<uint8_t>& 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<uint8_t>& aExpectedData) {
nsTArray<uint8_t> outputData;
nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData);
ASSERT_NS_SUCCEEDED(rv);
ValidateData(outputData, aExpectedData);
}
static std::pair<nsCOMPtr<nsIAsyncOutputStream>, nsCOMPtr<nsIAsyncInputStream>>
CreateStreamAndSendData(WebTransportStreamBase* aStream,
const nsTArray<uint8_t>& aData) {
nsCOMPtr<nsIAsyncOutputStream> writer;
nsCOMPtr<nsIAsyncInputStream> 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<uint8_t>& aExpectedData,
bool aExpectBidi) {
nsTArray<Capsule> 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<WebTransportStreamBase> CreateOutgoingStream(
MockWebTransportClient* aClient, bool aBidi = true) {
RefPtr<WebTransportStreamBase> stream;
auto callback =
[&](Result<RefPtr<WebTransportStreamBase>, 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
// Try to create bidi stream (should fail and trigger streams-blocked capsule)
RefPtr<WebTransportStreamBase> bidiStream;
client->Session()->CreateOutgoingBidirectionalStream([&](auto&& aResult) {
if (aResult.isOk()) {
bidiStream = aResult.unwrap();
}
});
ASSERT_TRUE(bidiStream == nullptr);
ServerProcessCapsules(server, client);
{
nsTArray<Capsule> 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<WebTransportStreamBase> unidiStream =
CreateOutgoingStream(client, false);
ASSERT_TRUE(unidiStream != nullptr);
nsTArray<uint8_t> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> bidiStream = CreateOutgoingStream(client);
ASSERT_TRUE(bidiStream != nullptr);
nsTArray<uint8_t> inputData;
CreateTestData(512, inputData);
CreateStreamAndSendData(bidiStream, inputData);
ServerProcessCapsules(server, client);
ValidateStreamCapsule(server, inputData, /* aExpectBidi = */ true);
// Echo back from server
nsTArray<uint8_t> echo;
echo.AppendElements(inputData.Elements(), inputData.Length());
StreamId id = StreamId::From(0);
server->SendWebTransportStreamDataCapsule(id, false, std::move(echo));
ClientProcessCapsules(server, client);
nsCOMPtr<nsIAsyncInputStream> reader;
nsCOMPtr<nsIAsyncOutputStream> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportSessionEventListener> listener =
new MockWebTransportSessionEventListener();
client->Session()->SetWebTransportSessionEventListener(listener);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
nsTArray<uint8_t> inputData;
CreateTestData(512, inputData);
nsTArray<uint8_t> cloned(inputData.Clone());
server->SendWebTransportStreamDataCapsule(1, false, std::move(cloned));
ClientProcessCapsules(server, client);
nsTArray<RefPtr<WebTransportStreamBase>> streams =
listener->TakeIncomingStreams();
ASSERT_EQ(streams.Length(), 1u);
nsCOMPtr<nsIAsyncOutputStream> writer;
nsCOMPtr<nsIAsyncInputStream> reader;
RefPtr<WebTransportStreamBase> 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<Http2WebTransportStream*>(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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> bidiStream = CreateOutgoingStream(client);
ASSERT_TRUE(bidiStream != nullptr);
nsCOMPtr<nsIAsyncOutputStream> writer;
nsCOMPtr<nsIAsyncInputStream> reader;
bidiStream->GetWriterAndReader(getter_AddRefs(writer),
getter_AddRefs(reader));
nsTArray<uint8_t> 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<Capsule> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> bidiStream = CreateOutgoingStream(client);
ASSERT_TRUE(bidiStream != nullptr);
nsCOMPtr<nsIAsyncOutputStream> writer;
nsCOMPtr<nsIAsyncInputStream> reader;
bidiStream->GetWriterAndReader(getter_AddRefs(writer),
getter_AddRefs(reader));
nsTArray<uint8_t> 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<Capsule> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> s1 = CreateOutgoingStream(client);
ASSERT_TRUE(s1 != nullptr);
RefPtr<WebTransportStreamBase> s2 = CreateOutgoingStream(client);
ASSERT_TRUE(s2 != nullptr);
CheckFc(client->Session()->ReceiverFc(), 0, 0);
CheckFc(*s1->ReceiverFc(), 0, 0);
CheckFc(*s2->ReceiverFc(), 0, 0);
nsTArray<uint8_t> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> bidiStream = CreateOutgoingStream(client);
ASSERT_TRUE(bidiStream != nullptr);
nsCOMPtr<nsIAsyncOutputStream> writer;
nsCOMPtr<nsIAsyncInputStream> reader;
bidiStream->GetWriterAndReader(getter_AddRefs(writer),
getter_AddRefs(reader));
nsTArray<uint8_t> 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<uint8_t> 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<Capsule> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> uniStream =
CreateOutgoingStream(client, false);
ASSERT_TRUE(uniStream != nullptr);
uniStream->SendStopSending(0);
ServerProcessCapsules(server, client);
nsTArray<Capsule> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportSessionEventListener> listener =
new MockWebTransportSessionEventListener();
client->Session()->SetWebTransportSessionEventListener(listener);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> uniStream =
CreateOutgoingStream(client, false);
ASSERT_TRUE(uniStream != nullptr);
nsTArray<uint8_t> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportSessionEventListener> listener =
new MockWebTransportSessionEventListener();
client->Session()->SetWebTransportSessionEventListener(listener);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> stream = CreateOutgoingStream(client);
ASSERT_TRUE(stream != nullptr);
nsTArray<uint8_t> 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<Capsule> 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<MockWebTransportClient> client = new MockWebTransportClient(settings);
RefPtr<MockWebTransportSessionEventListener> listener =
new MockWebTransportSessionEventListener();
client->Session()->SetWebTransportSessionEventListener(listener);
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<WebTransportStreamBase> stream = CreateOutgoingStream(client);
ASSERT_TRUE(stream != nullptr);
nsTArray<uint8_t> 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<MockWebTransportClient> client =
new MockWebTransportClient(Http2WebTransportInitialSettings());
RefPtr<MockWebTransportServer> server = new MockWebTransportServer();
RefPtr<MockWebTransportSessionEventListener> listener =
new MockWebTransportSessionEventListener();
client->Session()->SetWebTransportSessionEventListener(listener);
nsTArray<uint8_t> 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<Capsule> 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();
}