Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions moxygen/MoQFramer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bool isRequestSpecificParam(moxygen::TrackRequestParamKey key) {
case moxygen::TrackRequestParamKey::GROUP_ORDER:
case moxygen::TrackRequestParamKey::SUBSCRIBER_PRIORITY:
case moxygen::TrackRequestParamKey::FORWARD:
case moxygen::TrackRequestParamKey::NEW_GROUP_REQUEST:
return true;
default:
return false;
Expand Down Expand Up @@ -4452,6 +4453,16 @@ WriteResult MoQFrameWriter::writeSubscribeRequestHelper(
forwardParam.asUint64 = 0;
requestSpecificParams.push_back(forwardParam);
}

auto newGroupRequestValue = getFirstIntParam(
subscribeRequest.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
if (newGroupRequestValue.has_value()) {
Parameter newGroupRequestParam;
newGroupRequestParam.key =
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
newGroupRequestParam.asUint64 = *newGroupRequestValue;
requestSpecificParams.push_back(newGroupRequestParam);
}
} else {
writeVarint(
writeBuf,
Expand Down Expand Up @@ -4538,6 +4549,16 @@ WriteResult MoQFrameWriter::writeRequestUpdate(
forwardParam.asUint64 = *update.forward ? 1 : 0;
requestSpecificParams.push_back(forwardParam);
}

auto newGroupRequestValue = getFirstIntParam(
update.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
if (newGroupRequestValue.has_value()) {
Parameter newGroupRequestParam;
newGroupRequestParam.key =
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
newGroupRequestParam.asUint64 = *newGroupRequestValue;
requestSpecificParams.push_back(newGroupRequestParam);
}
} else {
// For draft < 15, start and endGroup are mandatory
XCHECK(update.start.has_value()) << "start is required for draft < 15";
Expand Down Expand Up @@ -4873,6 +4894,16 @@ WriteResult MoQFrameWriter::writePublishOk(
forwardParam.asUint64 = 0;
requestSpecificParams.push_back(forwardParam);
}

auto newGroupRequestValue = getFirstIntParam(
publishOk.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
if (newGroupRequestValue.has_value()) {
Parameter newGroupRequestParam;
newGroupRequestParam.key =
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
newGroupRequestParam.asUint64 = *newGroupRequestValue;
requestSpecificParams.push_back(newGroupRequestParam);
}
} else {
writeVarint(
writeBuf,
Expand Down
7 changes: 7 additions & 0 deletions moxygen/MoQTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ const folly::F14FastSet<FrameType> kAllowedFramesForForward = {
FrameType::PUBLISH_OK,
FrameType::SUBSCRIBE_NAMESPACE};

const folly::F14FastSet<FrameType> kAllowedFramesForNewGroupRequest = {
FrameType::SUBSCRIBE,
FrameType::REQUEST_UPDATE,
FrameType::PUBLISH_OK};

// Allowlist mapping: TrackRequestParamKey -> set of allowed FrameTypes
// Empty set means allowed for all frame types
const folly::F14FastMap<TrackRequestParamKey, folly::F14FastSet<FrameType>>
Expand All @@ -252,6 +257,8 @@ const folly::F14FastMap<TrackRequestParamKey, folly::F14FastSet<FrameType>>
{TrackRequestParamKey::GROUP_ORDER, kAllowedFramesForGroupOrder},
{TrackRequestParamKey::LARGEST_OBJECT, kAllowedFramesForLargestObject},
{TrackRequestParamKey::FORWARD, kAllowedFramesForForward},
{TrackRequestParamKey::NEW_GROUP_REQUEST,
kAllowedFramesForNewGroupRequest},
};

// Frame types that allow all parameters (no validation)
Expand Down
1 change: 1 addition & 0 deletions moxygen/MoQTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ enum class TrackRequestParamKey : uint64_t {
GROUP_ORDER = 0x22,
LARGEST_OBJECT = 0x9,
FORWARD = 0x10,
NEW_GROUP_REQUEST = 0x32,
};

class Parameters {
Expand Down
67 changes: 67 additions & 0 deletions moxygen/relay/MoQForwarder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ void MoQForwarder::setExtensions(Extensions extensions) {
}
}

void MoQForwarder::setOutstandingNewGroupRequest(uint64_t value) {
outstandingNewGroupRequest_ = value;
}

void MoQForwarder::setLargest(AbsoluteLocation largest) {
largest_ = largest;
}
Expand Down Expand Up @@ -361,6 +365,11 @@ void MoQForwarder::removeSubscriberIt(
void MoQForwarder::updateLargest(uint64_t group, uint64_t object) {
AbsoluteLocation now{group, object};
if (!largest_ || now > *largest_) {
// Clear any outstanding NEW_GROUP_REQUEST once the upstream group advances.
if (outstandingNewGroupRequest_.has_value() &&
(!largest_ || group > largest_->group)) {
outstandingNewGroupRequest_.reset();
}
largest_ = now;
}
}
Expand Down Expand Up @@ -529,6 +538,44 @@ void MoQForwarder::removeForwardingSubscriber() {
}
}

bool MoQForwarder::shouldForwardNewGroupRequest(uint64_t requestedGroup) const {
// the track must support dynamic groups.
if (!getPublisherDynamicGroups(extensions_).value_or(false)) {
return false;
}
// non-zero value <= LargestGroup means the new group is already
// available — do not send upstream.
if (requestedGroup != 0 && largest_.has_value() &&
requestedGroup <= largest_->group) {
return false;
}
// already have an outstanding request with >= value.
if (outstandingNewGroupRequest_.has_value() &&
*outstandingNewGroupRequest_ >= requestedGroup) {
return false;
}
return true;
}

void MoQForwarder::fireNewGroupRequest() {
if (callback_ && outstandingNewGroupRequest_.has_value()) {
callback_->newGroupRequested(this, *outstandingNewGroupRequest_);
}
}

void MoQForwarder::tryProcessNewGroupRequest(
const Parameters& params,
bool fire) {
auto value =
getFirstIntParam(params, TrackRequestParamKey::NEW_GROUP_REQUEST);
if (value.has_value() && shouldForwardNewGroupRequest(*value)) {
setOutstandingNewGroupRequest(*value);
if (fire) {
fireNewGroupRequest();
}
}
}

Payload MoQForwarder::maybeClone(const Payload& payload) {
return payload ? payload->clone() : nullptr;
}
Expand Down Expand Up @@ -597,13 +644,30 @@ void MoQForwarder::Subscriber::setParam(const TrackRequestParameter& param) {
}
}

void MoQForwarder::Subscriber::onPublishOk(const PublishOk& pubOk) {
// Update subscriber range from PUBLISH_OK
std::optional<AbsoluteLocation> end;
if (pubOk.endGroup) {
end = AbsoluteLocation{*pubOk.endGroup, 0};
}
range =
toSubscribeRange(pubOk.start, end, pubOk.locType, forwarder.largest());

// Update forward flag
shouldForward = pubOk.forward;

// Handle NEW_GROUP_REQUEST forwarding if present
forwarder.tryProcessNewGroupRequest(pubOk.params);
}

folly::coro::Task<folly::Expected<RequestOk, RequestError>>
MoQForwarder::Subscriber::requestUpdate(RequestUpdate requestUpdate) {
// Validation:
// - Start location can be updated
// - End location can increase or decrease
// - For bounded subscriptions (endGroup > 0), end must be >= start
// - Forward state is optional and only updated if explicitly provided
// - A New Group can be requested

// Only update start if provided
if (requestUpdate.start.has_value()) {
Expand Down Expand Up @@ -639,6 +703,9 @@ MoQForwarder::Subscriber::requestUpdate(RequestUpdate requestUpdate) {
forwarder.removeForwardingSubscriber();
}

// Only update new group request if provided
forwarder.tryProcessNewGroupRequest(requestUpdate.params);

co_return RequestOk{.requestID = requestUpdate.requestID};
}

Expand Down
20 changes: 20 additions & 0 deletions moxygen/relay/MoQForwarder.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class MoQForwarder : public TrackConsumer {
return extensions_;
}

// Extract the NEW_GROUP_REQUEST param from `params`, check if it should
// be forwarded upstream, record it as outstanding, and optionally fire
// the newGroupRequested callback. Pass fire=false when the NGR already
// rides an outgoing SUBSCRIBE and no extra REQUEST_UPDATE is needed.
void tryProcessNewGroupRequest(const Parameters& params, bool fire = true);


void setLargest(AbsoluteLocation largest);

std::optional<AbsoluteLocation> largest() {
Expand All @@ -60,6 +67,8 @@ class MoQForwarder : public TrackConsumer {
virtual ~Callback() = default;
virtual void onEmpty(MoQForwarder*) = 0;
virtual void forwardChanged(MoQForwarder*) {}
// This fires whenever an unseen NGR is received
virtual void newGroupRequested(MoQForwarder*, uint64_t group) {}
};

void setCallback(std::shared_ptr<Callback> callback);
Expand Down Expand Up @@ -107,6 +116,10 @@ class MoQForwarder : public TrackConsumer {
// requestID and trackAlias are placeholders (overwritten by MoQSession).
PublishRequest getPublishRequest() const;

// Process PUBLISH_OK response, updating range, forward flag, and handling
// NEW_GROUP_REQUEST forwarding via callback
void onPublishOk(const PublishOk& pubOk);

folly::coro::Task<folly::Expected<RequestOk, RequestError>> requestUpdate(
RequestUpdate requestUpdate) override;

Expand Down Expand Up @@ -306,6 +319,10 @@ class MoQForwarder : public TrackConsumer {
const MoQPublishError& err,
const std::string& callsite);

void setOutstandingNewGroupRequest(uint64_t value);
bool shouldForwardNewGroupRequest(uint64_t requestedGroup) const;
void fireNewGroupRequest();

FullTrackName fullTrackName_;
std::optional<TrackAlias> trackAlias_;
folly::F14FastMap<MoQSession*, std::shared_ptr<Subscriber>> subscribers_;
Expand All @@ -317,6 +334,9 @@ class MoQForwarder : public TrackConsumer {
GroupOrder groupOrder_{GroupOrder::OldestFirst};
std::optional<AbsoluteLocation> largest_;
Extensions extensions_;
// The NEW_GROUP_REQUEST value most recently forwarded upstream; cleared when
// the upstream Largest Group advances (indicating the request was fulfilled).
std::optional<uint64_t> outstandingNewGroupRequest_{};
std::shared_ptr<Callback> callback_;
uint64_t forwardingSubscribers_{0};
bool draining_{false};
Expand Down
54 changes: 47 additions & 7 deletions moxygen/relay/MoQRelay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "moxygen/relay/MoQRelay.h"
#include "moxygen/MoQFilters.h"
#include "moxygen/MoQTrackProperties.h"

namespace {
constexpr uint8_t kDefaultUpstreamPriority = 128;
Expand Down Expand Up @@ -34,6 +35,24 @@ folly::coro::Task<void> MoQRelay::doSubscribeUpdate(
}
}

// Sends a REQUEST_UPDATE that carries only the NEW_GROUP_REQUEST parameter.
folly::coro::Task<void> MoQRelay::doNewGroupRequestUpdate(
std::shared_ptr<Publisher::SubscriptionHandle> handle,
uint64_t newGroupRequestValue) {
XLOG(DBG4) << "Sending NEW_GROUP_REQUEST update: " << newGroupRequestValue;
RequestUpdate update;
update.requestID = RequestID(0);
update.existingRequestID = handle->subscribeOk().requestID;
update.params.insertParam(Parameter(
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST),
newGroupRequestValue));
auto updateRes = co_await handle->requestUpdate(std::move(update));
if (updateRes.hasError()) {
XLOG(ERR) << "NEW_GROUP_REQUEST update failed: "
<< updateRes.error().reasonPhrase;
}
}

std::shared_ptr<MoQRelay::NamespaceNode> MoQRelay::findNamespaceNode(
const TrackNamespace& ns,
bool createMissingNodes,
Expand Down Expand Up @@ -509,13 +528,10 @@ folly::coro::Task<void> MoQRelay::publishToSession(
guard.dismiss();
XLOG(DBG1) << "Publish OK sess=" << session.get();
auto& pubOk = pubResult.value().value();
std::optional<AbsoluteLocation> end;
if (pubOk.endGroup) {
end = AbsoluteLocation{*pubOk.endGroup, 0};
}
subscriber->range =
toSubscribeRange(pubOk.start, end, pubOk.locType, forwarder->largest());
subscriber->shouldForward = pubOk.forward;

// Process the PUBLISH_OK response - updates range, forward flag, and
// handles NEW_GROUP_REQUEST forwarding via callback
subscriber->onPublishOk(pubOk);
}

class MoQRelay::NamespaceSubscription
Expand Down Expand Up @@ -871,6 +887,8 @@ folly::coro::Task<Publisher::SubscribeResult> MoQRelay::subscribe(
auto& rsub = it->second;
rsub.requestID = subRes.value()->subscribeOk().requestID;
rsub.handle = std::move(subRes.value());
// Record NGR as outstanding (no fire — it rides the outgoing SUBSCRIBE).
forwarder->tryProcessNewGroupRequest(subReq.params, /*fire=*/false);
rsub.promise.setValue(folly::unit);
co_return subscriber;
} else {
Expand Down Expand Up @@ -911,6 +929,8 @@ folly::coro::Task<Publisher::SubscribeResult> MoQRelay::subscribe(
doSubscribeUpdate(subscriptionIt->second.handle, /*forward=*/true))
.start();
}

forwarder->tryProcessNewGroupRequest(subReq.params);
co_return subscriber;
}
}
Expand Down Expand Up @@ -1019,6 +1039,7 @@ folly::coro::Task<Publisher::TrackStatusResult> MoQRelay::trackStatus(
}
}


TrackStatusOk trackStatusOk;
trackStatusOk.requestID = trackStatus.requestID;
trackStatusOk.groupOrder = forwarder->groupOrder();
Expand Down Expand Up @@ -1110,4 +1131,23 @@ void MoQRelay::forwardChanged(MoQForwarder* forwarder) {
.start();
}

void MoQRelay::newGroupRequested(MoQForwarder* forwarder, uint64_t group) {
auto subscriptionIt = subscriptions_.find(forwarder->fullTrackName());
if (subscriptionIt == subscriptions_.end()) {
return;
}
auto& subscription = subscriptionIt->second;
// Check if handle is still valid (publisher may have terminated)
if (!subscription.handle) {
XLOG(DBG4) << "Ignoring NEW_GROUP_REQUEST for " << subscriptionIt->first
<< " - publisher terminated";
return;
}
XLOG(INFO) << "New group request detected for " << subscriptionIt->first;

auto exec = subscription.upstream->getExecutor();
co_withExecutor(exec, doNewGroupRequestUpdate(subscription.handle, group))
.start();
}

} // namespace moxygen
5 changes: 5 additions & 0 deletions moxygen/relay/MoQRelay.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class MoQRelay : public Publisher,

void onEmpty(MoQForwarder* forwarder) override;
void forwardChanged(MoQForwarder* forwarder) override;
void newGroupRequested(MoQForwarder* forwarder, uint64_t group) override;

folly::coro::Task<void> publishNamespaceToSession(
std::shared_ptr<MoQSession> session,
Expand All @@ -203,6 +204,10 @@ class MoQRelay : public Publisher,
std::shared_ptr<Publisher::SubscriptionHandle> handle,
bool forward);

folly::coro::Task<void> doNewGroupRequestUpdate(
std::shared_ptr<Publisher::SubscriptionHandle> handle,
uint64_t newGroupRequestValue);

void publishNamespaceDone(
const TrackNamespace& trackNamespace,
NamespaceNode* node);
Expand Down
Loading
Loading