/* 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