339 lines
8.9 KiB
C++
339 lines
8.9 KiB
C++
/* -*- 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
|