Skip to content

Commit dc8bdf7

Browse files
afrindmeta-codesync[bot]
authored andcommitted
Resolve publisher priority from track property extensions
Summary: Draft-16 moved PUBLISHER_PRIORITY from a request parameter to a track property extension, but setPublisherPriorityFromParams only ever looked at params. On draft-16+ that means the publisher never learns the priority it advertised, so it never elides it from subgroup headers or datagrams, and a subscriber talking to a peer that does elide it substitutes the default 128 for the real value. Both overloads now take the message's extensions alongside its params and check the extension first. The param fallback stays for a locally built message whose application inserted the raw param instead of calling the setter. Reviewed By: sandarsh Differential Revision: D116502271 fbshipit-source-id: 1935b9926c85f02bf7d38bdd914939e999633f87
1 parent a7e7ef3 commit dc8bdf7

5 files changed

Lines changed: 187 additions & 43 deletions

File tree

moxygen/MoQSession.cpp

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,7 +4305,8 @@ folly::coro::Task<void> MoQSession::handleSubscribe(
43054305
auto subOk = subHandle->subscribeOk();
43064306
subOk.requestID = requestID;
43074307

4308-
setPublisherPriorityFromParams(subOk.params, trackPublisher);
4308+
setPublisherPriorityFromParams(
4309+
subOk.params, subOk.extensions, trackPublisher);
43094310
trackPublisher->subscribeOkSent(subOk);
43104311

43114312
// TODO: verify TrackAlias is unique
@@ -4314,43 +4315,64 @@ folly::coro::Task<void> MoQSession::handleSubscribe(
43144315
}
43154316
}
43164317

4318+
// Extensions are canonical; the param fallback only fires for a locally built
4319+
// message that set the raw param.
4320+
static std::optional<uint64_t> getPublisherPriorityProperty(
4321+
const Extensions& trackProperties,
4322+
const TrackRequestParameters& params) {
4323+
auto publisherPriority = getPublisherPriority(trackProperties);
4324+
if (publisherPriority) {
4325+
return *publisherPriority;
4326+
}
4327+
return getFirstIntParam(params, TrackRequestParamKey::PUBLISHER_PRIORITY);
4328+
}
4329+
4330+
// nullopt leaves the priority unset, which disables elision on the publisher.
4331+
static std::optional<uint8_t> toPublisherPriority(uint64_t advertised) {
4332+
if (advertised > kMaxPriority) {
4333+
XLOG(WARN) << "Invalid publisher priority value: " << advertised;
4334+
return std::nullopt;
4335+
}
4336+
return static_cast<uint8_t>(advertised);
4337+
}
4338+
43174339
void MoQSession::setPublisherPriorityFromParams(
43184340
const TrackRequestParameters& params,
4341+
const Extensions& trackProperties,
43194342
const std::shared_ptr<TrackPublisherImpl>& trackPublisher) {
4320-
// Extract PUBLISHER_PRIORITY parameter if present (version 15+)
4321-
if (getDraftMajorVersion(*getNegotiatedVersion()) >= 15) {
4322-
auto publisherPriority =
4323-
getFirstIntParam(params, TrackRequestParamKey::PUBLISHER_PRIORITY);
4324-
if (publisherPriority) {
4325-
if (*publisherPriority <= 255) {
4326-
trackPublisher->setPublisherPriority(
4327-
static_cast<uint8_t>(*publisherPriority));
4328-
} else {
4329-
XLOG(WARN) << "Invalid priority value: "
4330-
<< static_cast<uint64_t>(*publisherPriority);
4331-
}
4332-
} else {
4333-
trackPublisher->setPublisherPriority(kDefaultPriority);
4334-
}
4343+
if (getDraftMajorVersion(*getNegotiatedVersion()) < 15) {
4344+
return;
4345+
}
4346+
auto advertised = getPublisherPriorityProperty(trackProperties, params);
4347+
if (!advertised) {
4348+
// Naming the default lets objects published at it elide the priority byte.
4349+
trackPublisher->setPublisherPriority(kDefaultPriority);
4350+
return;
4351+
}
4352+
auto publisherPriority = toPublisherPriority(*advertised);
4353+
if (publisherPriority) {
4354+
trackPublisher->setPublisherPriority(*publisherPriority);
43354355
}
43364356
}
43374357

43384358
void MoQSession::setPublisherPriorityFromParams(
43394359
const TrackRequestParameters& params,
4360+
const Extensions& trackProperties,
43404361
const std::shared_ptr<SubscribeTrackReceiveState>& trackReceiveState) {
4341-
// Extract PUBLISHER_PRIORITY parameter if present (version 15+)
4342-
if (getDraftMajorVersion(*getNegotiatedVersion()) >= 15) {
4343-
auto publisherPriority =
4344-
getFirstIntParam(params, TrackRequestParamKey::PUBLISHER_PRIORITY);
4345-
if (publisherPriority) {
4346-
if (*publisherPriority <= 255) {
4347-
trackReceiveState->setPublisherPriority(
4348-
static_cast<uint8_t>(*publisherPriority));
4349-
} else {
4350-
XLOG(ERR) << "Invalid priority value: "
4351-
<< static_cast<uint64_t>(*publisherPriority);
4352-
}
4353-
}
4362+
applyResolvedPublisherPriority(
4363+
getPublisherPriorityProperty(trackProperties, params), trackReceiveState);
4364+
}
4365+
4366+
// No default needed here; the receive state reads unset as kDefaultPriority.
4367+
void MoQSession::applyResolvedPublisherPriority(
4368+
std::optional<uint64_t> advertised,
4369+
const std::shared_ptr<SubscribeTrackReceiveState>& trackReceiveState) {
4370+
if (getDraftMajorVersion(*getNegotiatedVersion()) < 15 || !advertised) {
4371+
return;
4372+
}
4373+
auto publisherPriority = toPublisherPriority(*advertised);
4374+
if (publisherPriority) {
4375+
trackReceiveState->setPublisherPriority(*publisherPriority);
43544376
}
43554377
}
43564378

@@ -4686,7 +4708,8 @@ void MoQSession::onSubscribeOk(SubscribeOk subOk) {
46864708
close(SessionCloseErrorCode::DUPLICATE_TRACK_ALIAS);
46874709
}
46884710
auto trackAlias = subOk.trackAlias;
4689-
setPublisherPriorityFromParams(subOk.params, trackReceiveState);
4711+
setPublisherPriorityFromParams(
4712+
subOk.params, subOk.extensions, trackReceiveState);
46904713
trackReceiveState->processSubscribeOK(std::move(subOk));
46914714
if (trackReceiveState->getSubscribeCallback()) {
46924715
trackReceiveState->getSubscribeCallback()->setTrackAlias(trackAlias);
@@ -4884,11 +4907,12 @@ folly::coro::Task<void> MoQSession::handlePublish(
48844907
co_await folly::coro::co_safe_point;
48854908
folly::RequestContextScopeGuard guard;
48864909
setRequestSession();
4887-
// Capture params before moving publish
4910+
// Read what we need before publish is moved; copying Extensions clones IOBufs
48884911
auto requestID = publish.requestID;
48894912
auto alias = publish.trackAlias;
48904913
auto ftn = publish.fullTrackName;
4891-
auto params = publish.params;
4914+
auto publisherPriority =
4915+
getPublisherPriorityProperty(publish.extensions, publish.params);
48924916

48934917
// PubError if error occurs
48944918
auto publishErr =
@@ -4925,8 +4949,7 @@ folly::coro::Task<void> MoQSession::handlePublish(
49254949
auto trackReceiveState = std::make_shared<SubscribeTrackReceiveState>(
49264950
ftn, requestID, initiator.consumer, this, alias, logger_, true);
49274951

4928-
// Extract PUBLISHER_PRIORITY parameter if present (version 15+)
4929-
setPublisherPriorityFromParams(params, trackReceiveState);
4952+
applyResolvedPublisherPriority(publisherPriority, trackReceiveState);
49304953

49314954
initiator.consumer->setTrackAlias(alias);
49324955
subTracks_.emplace(alias, trackReceiveState);
@@ -5859,8 +5882,7 @@ Subscriber::PublishResult MoQSession::publish(
58595882
// Set publishHandle in trackPublisher so it can cancel on unsubscribes
58605883
trackPublisher->setSubscriptionHandle(std::move(handle));
58615884

5862-
// Extract PUBLISHER_PRIORITY parameter if present (version 15+)
5863-
setPublisherPriorityFromParams(pub.params, trackPublisher);
5885+
setPublisherPriorityFromParams(pub.params, pub.extensions, trackPublisher);
58645886

58655887
// Set reply context so sendPublishDone can write on the correct stream
58665888
auto& control = sendResult.value();

moxygen/MoQSession.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,10 +826,15 @@ class MoQSession : public Subscriber,
826826
const SetupParameters& params);
827827
void setPublisherPriorityFromParams(
828828
const TrackRequestParameters& params,
829+
const Extensions& trackProperties,
829830
const std::shared_ptr<TrackPublisherImpl>& trackPublisher);
830831
void setPublisherPriorityFromParams(
831832
const TrackRequestParameters& params,
833+
const Extensions& trackProperties,
832834
const std::shared_ptr<SubscribeTrackReceiveState>& trackPublisher);
835+
void applyResolvedPublisherPriority(
836+
std::optional<uint64_t> advertised,
837+
const std::shared_ptr<SubscribeTrackReceiveState>& trackReceiveState);
833838

834839
protected:
835840
// Protected members and methods for MoQRelaySession subclass access

moxygen/MoQTrackProperties.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ std::optional<uint64_t> getIntExtension(const Msg& msg, uint64_t type) {
4747
return msg.extensions.getIntExtension(type);
4848
}
4949

50-
// Overload for Extensions directly
50+
// Lets the getters below be called with a bare Extensions, which the relay
51+
// does when it only has the track properties and not the message.
5152
inline std::optional<uint64_t> getIntExtension(
5253
const Extensions& extensions,
5354
uint64_t type) {
@@ -142,14 +143,25 @@ std::optional<std::chrono::milliseconds> getPublisherMaxCacheDuration(
142143
return std::nullopt;
143144
}
144145

145-
// PUBLISHER_PRIORITY getter
146+
// PUBLISHER_PRIORITY getter. A value too wide for the wire is logged and
147+
// reported as absent, so callers fall back as they would for a peer that
148+
// advertised nothing.
149+
inline std::optional<uint8_t> getPublisherPriority(
150+
const Extensions& extensions) {
151+
auto val = extensions.getIntExtension(kPublisherPriorityExtensionType);
152+
if (!val) {
153+
return std::nullopt;
154+
}
155+
if (*val > kMaxPriority) {
156+
XLOG(WARN) << "Invalid publisher priority extension: " << *val;
157+
return std::nullopt;
158+
}
159+
return static_cast<uint8_t>(*val);
160+
}
161+
146162
template <typename Msg>
147163
std::optional<uint8_t> getPublisherPriority(const Msg& msg) {
148-
auto val = detail::getIntExtension(msg, kPublisherPriorityExtensionType);
149-
if (val && *val <= 255) {
150-
return static_cast<uint8_t>(*val);
151-
}
152-
return std::nullopt;
164+
return getPublisherPriority(msg.extensions);
153165
}
154166

155167
// GROUP_ORDER getter

moxygen/MoQTypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace moxygen {
2121
//////// Constants ////////
2222
const size_t kMaxNamespaceLength = 32;
2323
const uint8_t kDefaultPriority = 128;
24+
const uint64_t kMaxPriority = 255;
2425

2526
// From QUIC, until MOQ supports 64 bit varint
2627
constexpr uint64_t kEightByteLimit = 0x3FFFFFFFFFFFFFFF;

moxygen/test/MoQSessionObjectDeliveryTests.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,110 @@ CO_TEST_P_X(MoQSessionTest, SubgroupWireFormatHintsRoundTrip) {
262262
co_await publishDone_;
263263
serverSession_->close(SessionCloseErrorCode::NO_ERROR);
264264
}
265+
CO_TEST_P_X(MoQSessionTest, AdvertisedPublisherPriorityRoundTrips) {
266+
// The publisher advertises a non-default priority on the SubscribeOk (a
267+
// track property extension from draft 16, a param before that) and then
268+
// publishes at exactly that priority, so draft-15+ elides it from the
269+
// subgroup header. Both ends have to resolve the advertised value: if only
270+
// the sender honours it, the subscriber backfills the protocol default and
271+
// the two disagree about every object's priority.
272+
constexpr uint8_t kAdvertisedPriority = 100;
273+
auto majorVersion = getDraftMajorVersion(GetParam().serverVersion);
274+
co_await setupMoQSession();
275+
expectSubscribe(
276+
[this, majorVersion, priority = kAdvertisedPriority](
277+
auto sub, auto pub) -> TaskSubscribeResult {
278+
eventBase_.add([pub, sub, priority] {
279+
auto sgp = pub->beginSubgroup(0, 0, priority).value();
280+
sgp->object(0, moxygen::test::makeBuf(10), noExtensions(), true);
281+
pub->publishDone(getTrackEndedPublishDone(sub.requestID));
282+
});
283+
co_return makeSubscribeOkResult(
284+
sub, std::nullopt, priority, majorVersion);
285+
},
286+
MoQControlCodec::Direction::CLIENT);
287+
288+
auto sg = std::make_shared<testing::StrictMock<MockSubgroupConsumer>>();
289+
EXPECT_CALL(*subscribeCallback_, beginSubgroup(0, 0, kAdvertisedPriority, _))
290+
.WillOnce(testing::Return(sg));
291+
EXPECT_CALL(*sg, object(0, _, _, true))
292+
.WillOnce(testing::Return(folly::unit));
293+
294+
expectPublishDone(MoQControlCodec::Direction::SERVER);
295+
auto res = co_await serverSession_->subscribe(
296+
getSubscribe(kTestTrackName), subscribeCallback_);
297+
co_await publishDone_;
298+
serverSession_->close(SessionCloseErrorCode::NO_ERROR);
299+
}
300+
CO_TEST_P_X(MoQSessionTest, ElidedPriorityResolvesToAdvertisedPriority) {
301+
// A conforming peer that advertises a publisher priority may omit it from
302+
// every subgroup header. Write such a header by hand -- a moxygen publisher
303+
// only elides once it has resolved the advertised value, which is the very
304+
// thing under test -- and check the subscriber recovers the advertised
305+
// priority rather than falling back to the protocol default.
306+
constexpr uint8_t kAdvertisedPriority = 100;
307+
auto version = GetParam().serverVersion;
308+
if (getDraftMajorVersion(version) < 16) {
309+
// Before draft 16 the priority rides a param, not a track property
310+
co_return;
311+
}
312+
co_await setupMoQSession();
313+
314+
expectSubscribe(
315+
[version, priority = kAdvertisedPriority](
316+
auto sub, auto /*pub*/) -> TaskSubscribeResult {
317+
co_return makeSubscribeOkResult(
318+
sub,
319+
AbsoluteLocation{0, 0},
320+
priority,
321+
getDraftMajorVersion(version));
322+
},
323+
MoQControlCodec::Direction::CLIENT);
324+
325+
auto sg = std::make_shared<testing::StrictMock<MockSubgroupConsumer>>();
326+
folly::coro::Baton objectReceived;
327+
EXPECT_CALL(*subscribeCallback_, beginSubgroup(0, 0, kAdvertisedPriority, _))
328+
.WillOnce(testing::Return(sg));
329+
EXPECT_CALL(*sg, object(0, _, _, _))
330+
.WillOnce([&](auto, auto, const auto&, auto) {
331+
objectReceived.post();
332+
return folly::unit;
333+
});
334+
EXPECT_CALL(*sg, reset(_));
335+
336+
auto res = co_await serverSession_->subscribe(
337+
getSubscribe(kTestTrackName), subscribeCallback_);
338+
EXPECT_FALSE(res.hasError());
339+
340+
// Publisher side writes a subgroup whose header carries no priority
341+
auto wh = CHECK_NOTNULL(clientWt_->createUniStream().value_or(nullptr));
342+
folly::IOBufQueue dataBuf{folly::IOBufQueue::cacheChainLength()};
343+
MoQFrameWriter writer;
344+
writer.initializeVersion(version);
345+
ObjectHeader objHeader(0, 0, 0, std::nullopt, ObjectStatus::NORMAL);
346+
objHeader.length = 5;
347+
writer.writeSubgroupHeader(
348+
dataBuf,
349+
res.value()->subscribeOk().trackAlias,
350+
objHeader,
351+
SubgroupIDFormat::Present,
352+
false);
353+
writer.writeStreamObject(
354+
dataBuf,
355+
getSubgroupStreamType(
356+
version,
357+
SubgroupIDFormat::Present,
358+
false,
359+
false,
360+
/*priorityPresent=*/false),
361+
objHeader,
362+
makeBuf(5));
363+
wh->writeStreamData(dataBuf.move(), false, nullptr);
364+
365+
co_await objectReceived;
366+
res.value()->unsubscribe();
367+
serverSession_->close(SessionCloseErrorCode::NO_ERROR);
368+
}
265369
CO_TEST_P_X(MoQSessionTest, MixSubgroupsAndDatagrams) {
266370
// Test that subgroups and datagrams can be mixed on the same track
267371
co_await setupMoQSession();

0 commit comments

Comments
 (0)