Implement MoQT priority calculation.
The scheme implements priority assignment that follows the semantics of https://github.com/moq-wg/moq-transport/pull/470 and is compatible with how WebTransport scheduling works.
PiperOrigin-RevId: 651476056
diff --git a/build/source_list.bzl b/build/source_list.bzl
index c93905d..34b592f 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1512,6 +1512,7 @@
"quic/moqt/moqt_messages.h",
"quic/moqt/moqt_outgoing_queue.h",
"quic/moqt/moqt_parser.h",
+ "quic/moqt/moqt_priority.h",
"quic/moqt/moqt_session.h",
"quic/moqt/moqt_subscribe_windows.h",
"quic/moqt/moqt_track.h",
@@ -1530,6 +1531,8 @@
"quic/moqt/moqt_outgoing_queue_test.cc",
"quic/moqt/moqt_parser.cc",
"quic/moqt/moqt_parser_test.cc",
+ "quic/moqt/moqt_priority.cc",
+ "quic/moqt/moqt_priority_test.cc",
"quic/moqt/moqt_session.cc",
"quic/moqt/moqt_session_test.cc",
"quic/moqt/moqt_subscribe_windows.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 634b199..23437ab 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1516,6 +1516,7 @@
"src/quiche/quic/moqt/moqt_messages.h",
"src/quiche/quic/moqt/moqt_outgoing_queue.h",
"src/quiche/quic/moqt/moqt_parser.h",
+ "src/quiche/quic/moqt/moqt_priority.h",
"src/quiche/quic/moqt/moqt_session.h",
"src/quiche/quic/moqt/moqt_subscribe_windows.h",
"src/quiche/quic/moqt/moqt_track.h",
@@ -1534,6 +1535,8 @@
"src/quiche/quic/moqt/moqt_outgoing_queue_test.cc",
"src/quiche/quic/moqt/moqt_parser.cc",
"src/quiche/quic/moqt/moqt_parser_test.cc",
+ "src/quiche/quic/moqt/moqt_priority.cc",
+ "src/quiche/quic/moqt/moqt_priority_test.cc",
"src/quiche/quic/moqt/moqt_session.cc",
"src/quiche/quic/moqt/moqt_session_test.cc",
"src/quiche/quic/moqt/moqt_subscribe_windows.cc",
diff --git a/build/source_list.json b/build/source_list.json
index dc343b0..1b9a333 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1515,6 +1515,7 @@
"quiche/quic/moqt/moqt_messages.h",
"quiche/quic/moqt/moqt_outgoing_queue.h",
"quiche/quic/moqt/moqt_parser.h",
+ "quiche/quic/moqt/moqt_priority.h",
"quiche/quic/moqt/moqt_session.h",
"quiche/quic/moqt/moqt_subscribe_windows.h",
"quiche/quic/moqt/moqt_track.h",
@@ -1533,6 +1534,8 @@
"quiche/quic/moqt/moqt_outgoing_queue_test.cc",
"quiche/quic/moqt/moqt_parser.cc",
"quiche/quic/moqt/moqt_parser_test.cc",
+ "quiche/quic/moqt/moqt_priority.cc",
+ "quiche/quic/moqt/moqt_priority_test.cc",
"quiche/quic/moqt/moqt_session.cc",
"quiche/quic/moqt/moqt_session_test.cc",
"quiche/quic/moqt/moqt_subscribe_windows.cc",
diff --git a/quiche/quic/moqt/moqt_priority.cc b/quiche/quic/moqt/moqt_priority.cc
new file mode 100644
index 0000000..5528079
--- /dev/null
+++ b/quiche/quic/moqt/moqt_priority.cc
@@ -0,0 +1,70 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/moqt/moqt_priority.h"
+
+#include <cstdint>
+#include <limits>
+
+#include "quiche/web_transport/web_transport.h"
+
+namespace moqt {
+
+namespace {
+template <uint64_t NumBits>
+constexpr uint64_t Flip(uint64_t number) {
+ static_assert(NumBits <= 63);
+ return (1ull << NumBits) - 1 - number;
+}
+template <uint64_t N>
+constexpr uint64_t OnlyLowestNBits(uint64_t value) {
+ static_assert(N <= 62);
+ return value & ((1ull << (N + 1)) - 1);
+}
+} // namespace
+
+// The send order is packed into a signed 64-bit integer as follows:
+// 63: always zero to indicate a positive number
+// 62: 0 for data streams, 1 for control streams
+// 54-61: subscriber priority
+// 46-53: publisher priority
+// (if stream-per-group)
+// 0-45: group ID
+// (if stream-per-object)
+// 20-45: group ID
+// 0-19: object ID
+
+webtransport::SendOrder SendOrderForStream(MoqtPriority subscriber_priority,
+ MoqtPriority publisher_priority,
+ uint64_t group_id,
+ MoqtDeliveryOrder delivery_order) {
+ const int64_t track_bits = (Flip<8>(subscriber_priority) << 54) |
+ (Flip<8>(publisher_priority) << 46);
+ group_id = OnlyLowestNBits<46>(group_id);
+ if (delivery_order == MoqtDeliveryOrder::kAscending) {
+ group_id = Flip<46>(group_id);
+ }
+ return track_bits | group_id;
+}
+
+webtransport::SendOrder SendOrderForStream(MoqtPriority subscriber_priority,
+ MoqtPriority publisher_priority,
+ uint64_t group_id,
+ uint64_t object_id,
+ MoqtDeliveryOrder delivery_order) {
+ const int64_t track_bits = (Flip<8>(subscriber_priority) << 54) |
+ (Flip<8>(publisher_priority) << 46);
+ group_id = OnlyLowestNBits<26>(group_id);
+ object_id = OnlyLowestNBits<20>(object_id);
+ if (delivery_order == MoqtDeliveryOrder::kAscending) {
+ group_id = Flip<26>(group_id);
+ }
+ object_id = Flip<20>(object_id); // Object ID is always ascending.
+ return track_bits | (group_id << 20) | object_id;
+}
+
+const webtransport::SendOrder kMoqtControlStreamSendOrder =
+ std::numeric_limits<webtransport::SendOrder>::max();
+
+} // namespace moqt
diff --git a/quiche/quic/moqt/moqt_priority.h b/quiche/quic/moqt/moqt_priority.h
new file mode 100644
index 0000000..f8c8ad9
--- /dev/null
+++ b/quiche/quic/moqt/moqt_priority.h
@@ -0,0 +1,40 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_MOQT_MOQT_PRIORITY_H_
+#define QUICHE_QUIC_MOQT_MOQT_PRIORITY_H_
+
+#include <cstdint>
+
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace moqt {
+
+// Priority that can be assigned to a track or individual streams associated
+// with the track by either the publisher or the subscriber.
+using MoqtPriority = uint8_t;
+
+// Indicates the desired order of delivering groups associated with a given
+// track.
+enum class MoqtDeliveryOrder : uint8_t {
+ kAscending,
+ kDescending,
+};
+
+// Computes WebTransport send order for an MoQT data stream with the specified
+// parameters.
+QUICHE_EXPORT webtransport::SendOrder SendOrderForStream(
+ MoqtPriority subscriber_priority, MoqtPriority publisher_priority,
+ uint64_t group_id, MoqtDeliveryOrder delivery_order);
+QUICHE_EXPORT webtransport::SendOrder SendOrderForStream(
+ MoqtPriority subscriber_priority, MoqtPriority publisher_priority,
+ uint64_t group_id, uint64_t object_id, MoqtDeliveryOrder delivery_order);
+
+// WebTransport send order set on the MoQT control stream.
+QUICHE_EXPORT extern const webtransport::SendOrder kMoqtControlStreamSendOrder;
+
+} // namespace moqt
+
+#endif // QUICHE_QUIC_MOQT_MOQT_PRIORITY_H_
diff --git a/quiche/quic/moqt/moqt_priority_test.cc b/quiche/quic/moqt/moqt_priority_test.cc
new file mode 100644
index 0000000..d30c07c
--- /dev/null
+++ b/quiche/quic/moqt/moqt_priority_test.cc
@@ -0,0 +1,59 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/moqt/moqt_priority.h"
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace moqt {
+namespace {
+
+TEST(MoqtPrioirtyTest, TrackPriorities) {
+ // MoQT track priorities are descending (0 is highest), but WebTransport send
+ // order is ascending.
+ EXPECT_GT(SendOrderForStream(0x10, 0x80, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x80, 0, MoqtDeliveryOrder::kAscending));
+ EXPECT_GT(SendOrderForStream(0x80, 0x10, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x80, 0, MoqtDeliveryOrder::kAscending));
+ // Subscriber priority takes precedence over the sender priority.
+ EXPECT_GT(SendOrderForStream(0x10, 0x80, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x10, 0, MoqtDeliveryOrder::kAscending));
+ // Test extreme priority values (0x00 and 0xff).
+ EXPECT_GT(SendOrderForStream(0x00, 0x80, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0xff, 0x80, 0, MoqtDeliveryOrder::kAscending));
+ EXPECT_GT(SendOrderForStream(0x80, 0x00, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0xff, 0, MoqtDeliveryOrder::kAscending));
+}
+
+TEST(MoqtPrioirtyTest, ControlStream) {
+ EXPECT_GT(kMoqtControlStreamSendOrder,
+ SendOrderForStream(0x00, 0x00, 0, MoqtDeliveryOrder::kAscending));
+}
+
+TEST(MoqtPriorityTest, StreamPerGroup) {
+ EXPECT_GT(SendOrderForStream(0x80, 0x80, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x80, 1, MoqtDeliveryOrder::kAscending));
+ EXPECT_GT(SendOrderForStream(0x80, 0x80, 1, MoqtDeliveryOrder::kDescending),
+ SendOrderForStream(0x80, 0x80, 0, MoqtDeliveryOrder::kDescending));
+}
+
+TEST(MoqtPriorityTest, StreamPerObject) {
+ // Objects within the same group.
+ EXPECT_GT(
+ SendOrderForStream(0x80, 0x80, 0, 0, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x80, 0, 1, MoqtDeliveryOrder::kAscending));
+ EXPECT_GT(
+ SendOrderForStream(0x80, 0x80, 0, 0, MoqtDeliveryOrder::kDescending),
+ SendOrderForStream(0x80, 0x80, 0, 1, MoqtDeliveryOrder::kDescending));
+ // Objects of different groups.
+ EXPECT_GT(
+ SendOrderForStream(0x80, 0x80, 0, 1, MoqtDeliveryOrder::kAscending),
+ SendOrderForStream(0x80, 0x80, 1, 0, MoqtDeliveryOrder::kAscending));
+ EXPECT_GT(
+ SendOrderForStream(0x80, 0x80, 1, 1, MoqtDeliveryOrder::kDescending),
+ SendOrderForStream(0x80, 0x80, 0, 0, MoqtDeliveryOrder::kDescending));
+}
+
+} // namespace
+} // namespace moqt