Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; 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/. */
#ifndef mozilla_net_WebTransportFlowControl_h
#define mozilla_net_WebTransportFlowControl_h
#include "Capsule.h"
#include "CapsuleEncoder.h"
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "mozilla/net/neqo_glue_ffi_generated.h"
#include "WebTransportStreamBase.h"
namespace mozilla::net {
// This is based on `fc::SenderFlowControl` in neqo. Ideally, we would reuse it,
// but `SenderFlowControl` is in a private crate and tightly integrated with
// other internal crates in neqo.
class SenderFlowControlBase {
public:
explicit SenderFlowControlBase(uint64_t aInitial) : mLimit(aInitial) {}
bool Update(uint64_t aNewLimit) {
MOZ_ASSERT(aNewLimit < UINT64_MAX);
if (aNewLimit > mLimit) {
mLimit = aNewLimit;
mBlockedCapsule = false;
return true;
}
return false;
}
void Consume(uint64_t aCount) {
MOZ_ASSERT(mUsed + aCount <= mLimit);
mUsed += aCount;
}
uint64_t Available() const { return mLimit - mUsed; }
uint64_t Used() const { return mUsed; }
void Blocked() {
if (mLimit >= mBlockedAt) {
mBlockedAt = mLimit + 1;
mBlockedCapsule = true;
}
}
// Return whether a blocking Capsule needs to be sent.
// This is `Some` with the active limit if `blocked` has been called,
// if a blocking frame has not been sent (or it has been lost), and
// if the blocking condition remains.
mozilla::Maybe<uint64_t> BlockedNeeded() const {
if (mBlockedCapsule && mLimit < mBlockedAt) {
return Some(mBlockedAt - 1);
}
return Nothing();
}
void BlockedSent() { mBlockedCapsule = false; }
protected:
uint64_t mLimit = 0;
uint64_t mUsed = 0;
uint64_t mBlockedAt = 0;
bool mBlockedCapsule = false;
};
// Flow control for stream creation.
class SenderFlowControlStreamType : public SenderFlowControlBase {
public:
SenderFlowControlStreamType(WebTransportStreamType aType, uint64_t aInitial)
: SenderFlowControlBase(aInitial), mType(aType) {}
Maybe<CapsuleEncoder> CreateStreamsBlockedCapsule();
private:
WebTransportStreamType mType;
};
// Flow control for stream data.
class SenderFlowControlStreamId : public SenderFlowControlBase {
public:
SenderFlowControlStreamId(StreamId aId, uint64_t aInitial)
: SenderFlowControlBase(aInitial), mId(aId) {}
Maybe<CapsuleEncoder> CreateStreamDataBlockedCapsule();
private:
StreamId mId;
};
// Flow control for session data.
class SenderFlowControlSession : public SenderFlowControlBase {
public:
explicit SenderFlowControlSession(uint64_t aInitial)
: SenderFlowControlBase(aInitial) {}
Maybe<CapsuleEncoder> CreateSessionDataBlockedCapsule();
};
class LocalStreamLimits {
public:
LocalStreamLimits()
: mBidirectional(WebTransportStreamType::BiDi, 0),
mUnidirectional(WebTransportStreamType::UniDi, 0) {}
mozilla::Maybe<StreamId> TakeStreamId(WebTransportStreamType aStreamType) {
SenderFlowControlStreamType& fc =
(aStreamType == WebTransportStreamType::BiDi) ? mBidirectional
: mUnidirectional;
if (fc.Available() > 0) {
uint64_t newId = fc.Used();
fc.Consume(1);
uint64_t typeBit = (aStreamType == WebTransportStreamType::BiDi) ? 0 : 2;
return Some(StreamId((newId << 2) + typeBit));
} else {
fc.Blocked();
return Nothing();
}
}
const SenderFlowControlStreamType& operator[](
WebTransportStreamType aStreamType) const {
if (aStreamType == WebTransportStreamType::BiDi) {
return mBidirectional;
}
MOZ_ASSERT(aStreamType == WebTransportStreamType::UniDi);
return mUnidirectional;
}
SenderFlowControlStreamType& operator[](WebTransportStreamType aStreamType) {
if (aStreamType == WebTransportStreamType::BiDi) {
return mBidirectional;
}
MOZ_ASSERT(aStreamType == WebTransportStreamType::UniDi);
return mUnidirectional;
}
private:
SenderFlowControlStreamType mBidirectional;
SenderFlowControlStreamType mUnidirectional;
};
class ReceiverFlowControlBase {
public:
explicit ReceiverFlowControlBase(uint64_t aMax)
: mMaxActive(aMax), mMaxAllowed(aMax) {}
void Retire(uint64_t aRetired) {
if (aRetired <= mRetired) {
return;
}
mRetired = aRetired;
if (mRetired + mMaxActive / 2 > mMaxAllowed) {
mCapsulePending = true;
}
}
void SendFlowControlUpdate() {
if (mRetired + mMaxActive > mMaxAllowed) {
mCapsulePending = true;
}
}
bool CapsuleNeeded() const { return mCapsulePending; }
uint64_t NextLimit() const { return mRetired + mMaxActive; }
uint64_t MaxActive() const { return mMaxActive; }
void SetMaxActive(uint64_t aMax) {
mCapsulePending |= (mMaxActive < aMax);
mMaxActive = aMax;
}
uint64_t Retired() const { return mRetired; }
uint64_t Consumed() const { return mConsumed; }
void CapsuleSent(uint64_t aNewMax) {
mMaxAllowed = aNewMax;
mCapsulePending = false;
}
protected:
uint64_t mMaxActive = 0;
uint64_t mMaxAllowed = 0;
uint64_t mConsumed = 0;
uint64_t mRetired = 0;
bool mCapsulePending = false;
};
class ReceiverFlowControlStreamId : public ReceiverFlowControlBase {
public:
ReceiverFlowControlStreamId(StreamId aId, uint64_t aMax)
: ReceiverFlowControlBase(aMax), mId(aId) {}
Maybe<CapsuleEncoder> CreateMaxStreamDataCapsule();
Result<uint64_t, nsresult> SetConsumed(uint64_t aConsumed) {
if (aConsumed <= mConsumed) {
return 0;
}
if (aConsumed > mMaxAllowed) {
return Err(NS_ERROR_NOT_AVAILABLE);
}
uint64_t newConsumed = aConsumed - mConsumed;
mConsumed = aConsumed;
return newConsumed;
}
void AddRetired(uint64_t aCount) {
MOZ_ASSERT(mRetired + aCount <= mConsumed);
mRetired += aCount;
if (mRetired + mMaxActive / 2 > mMaxAllowed) {
mCapsulePending = true;
}
}
private:
StreamId mId;
};
class ReceiverFlowControlSession : public ReceiverFlowControlBase {
public:
explicit ReceiverFlowControlSession(uint64_t aMax)
: ReceiverFlowControlBase(aMax) {}
Maybe<CapsuleEncoder> CreateMaxDataCapsule();
// Return false when exceeding the flow control limit.
bool Consume(uint64_t aCount) {
if (mConsumed + aCount > mMaxAllowed) {
return false;
}
mConsumed += aCount;
return true;
}
void AddRetired(uint64_t aCount) {
MOZ_ASSERT(mRetired + aCount <= mConsumed);
mRetired += aCount;
if (mRetired + mMaxActive / 2 > mMaxAllowed) {
mCapsulePending = true;
}
}
};
class ReceiverFlowControlStreamType : public ReceiverFlowControlBase {
public:
ReceiverFlowControlStreamType(WebTransportStreamType aStreamType,
uint64_t aMax)
: ReceiverFlowControlBase(aMax), mType(aStreamType) {}
Maybe<CapsuleEncoder> CreateMaxStreamsCapsule();
bool CheckAllowed(uint64_t aNewEnd) const { return aNewEnd < mMaxAllowed; }
void AddRetired(uint64_t aCount) {
mRetired += aCount;
if (aCount > 0) {
SendFlowControlUpdate();
}
}
private:
WebTransportStreamType mType = WebTransportStreamType::BiDi;
};
class RemoteStreamLimit {
public:
RemoteStreamLimit(WebTransportStreamType aStreamType, uint64_t aMaxStreams)
: mStreamsFC(aStreamType, aMaxStreams) {
uint64_t typeBit = (aStreamType == WebTransportStreamType::BiDi) ? 0 : 2;
// Server initiated stream starts with 1.
mNextStreamId = StreamId(typeBit + 1);
}
bool IsAllowed(StreamId aStreamId) const {
uint64_t streamIndex = aStreamId >> 2;
return mStreamsFC.CheckAllowed(streamIndex);
}
Result<bool, nsresult> IsNewStream(StreamId aStreamId) const {
if (!IsAllowed(aStreamId)) {
return Err(NS_ERROR_NOT_AVAILABLE);
}
return aStreamId >= mNextStreamId;
}
StreamId TakeStreamId() {
StreamId newStream = mNextStreamId;
mNextStreamId.Next();
MOZ_ASSERT(IsAllowed(newStream));
return newStream;
}
ReceiverFlowControlStreamType& FlowControl() { return mStreamsFC; }
const ReceiverFlowControlStreamType& FlowControl() const {
return mStreamsFC;
}
private:
ReceiverFlowControlStreamType mStreamsFC;
StreamId mNextStreamId{1u};
};
class RemoteStreamLimits {
public:
RemoteStreamLimits(uint64_t aBidiMax, uint64_t aUniMax)
: mBidi(WebTransportStreamType::BiDi, aBidiMax),
mUni(WebTransportStreamType::UniDi, aUniMax) {}
RemoteStreamLimit& operator[](WebTransportStreamType aType) {
return aType == WebTransportStreamType::BiDi ? mBidi : mUni;
}
const RemoteStreamLimit& operator[](WebTransportStreamType aType) const {
return aType == WebTransportStreamType::BiDi ? mBidi : mUni;
}
private:
RemoteStreamLimit mBidi;
RemoteStreamLimit mUni;
};
} // namespace mozilla::net
#endif