|
5 | 5 |
|
6 | 6 | #include <chrono> |
7 | 7 | #include <cstdint> |
| 8 | +#include <functional> |
8 | 9 | #include <iomanip> |
9 | 10 | #include <iostream> |
10 | 11 | #include <map> |
@@ -88,7 +89,11 @@ struct MockTransport final : PublisherTransport { |
88 | 89 | std::vector<std::uint8_t>& bytes, |
89 | 90 | bool& fin, |
90 | 91 | std::chrono::milliseconds timeout) override { |
91 | | - static_cast<void>(timeout); |
| 92 | + read_timeouts.push_back(timeout); |
| 93 | + ++read_count; |
| 94 | + if (on_read) { |
| 95 | + on_read(*this, stream_id); |
| 96 | + } |
92 | 97 | const auto it = reads.find(stream_id); |
93 | 98 | if (it == reads.end()) { |
94 | 99 | return TransportStatus::failure("no queued read for stream"); |
@@ -124,8 +129,11 @@ struct MockTransport final : PublisherTransport { |
124 | 129 | std::uint64_t next_bidi_ = 0; |
125 | 130 | std::uint64_t next_uni_ = 2; |
126 | 131 | std::uint64_t last_close_code = 0; |
| 132 | + std::size_t read_count = 0; |
127 | 133 | std::vector<WriteEvent> writes; |
| 134 | + std::vector<std::chrono::milliseconds> read_timeouts; |
128 | 135 | std::map<std::uint64_t, std::vector<std::vector<std::uint8_t>>> reads; |
| 136 | + std::function<void(const MockTransport&, std::uint64_t)> on_read; |
129 | 137 | }; |
130 | 138 |
|
131 | 139 | void append_be16(std::vector<std::uint8_t>& out, std::uint16_t value) { |
@@ -554,6 +562,64 @@ int main() { |
554 | 562 | ok &= expect(transport.writes[8].bytes == std::vector<std::uint8_t>({0x09, 0x00, 0x09, 0x01, 0x07, 0x69, 0x6e, |
555 | 563 | 0x74, 0x65, 0x72, 0x6f, 0x70}), |
556 | 564 | "expected draft-14 PUBLISH_NAMESPACE_DONE to contain the configured track namespace"); |
| 565 | + ok &= expect(!transport.read_timeouts.empty() && transport.read_timeouts.back() == std::chrono::seconds(3), |
| 566 | + "expected default subscriber wait timeout to be 3 seconds"); |
| 567 | + } |
| 568 | + |
| 569 | + { |
| 570 | + MockTransport transport; |
| 571 | + transport.reads[0].push_back(encode_server_setup_message({ |
| 572 | + .draft = DraftVersion::kDraft14, |
| 573 | + .max_request_id = 8, |
| 574 | + })); |
| 575 | + transport.reads[0].push_back(encode_publish_namespace_ok_message(DraftVersion::kDraft14, 0)); |
| 576 | + transport.reads[0].push_back(encode_subscribe_message(2, kTestTrackNamespace, "catalog", 0)); |
| 577 | + transport.reads[0].push_back(encode_subscribe_message(4, kTestTrackNamespace, "vide_1", 0)); |
| 578 | + |
| 579 | + bool saw_media_before_media_subscribe = false; |
| 580 | + transport.on_read = [&](const MockTransport& current, std::uint64_t stream_id) { |
| 581 | + if (stream_id != 0 || current.read_count != 4) { |
| 582 | + return; |
| 583 | + } |
| 584 | + |
| 585 | + for (const auto& write : current.writes) { |
| 586 | + if (write.stream_id != 6) { |
| 587 | + continue; |
| 588 | + } |
| 589 | + std::uint64_t stream_type = 0; |
| 590 | + std::uint64_t track_alias = 0; |
| 591 | + std::uint64_t group_id = 0; |
| 592 | + std::uint64_t subgroup_id = 0; |
| 593 | + std::uint64_t publisher_priority = 0; |
| 594 | + std::uint64_t object_id_delta = 0; |
| 595 | + std::uint64_t payload_length = 0; |
| 596 | + std::vector<std::uint8_t> payload; |
| 597 | + if (decode_object_stream_fields(write.bytes, |
| 598 | + stream_type, |
| 599 | + track_alias, |
| 600 | + group_id, |
| 601 | + subgroup_id, |
| 602 | + publisher_priority, |
| 603 | + object_id_delta, |
| 604 | + payload_length, |
| 605 | + payload) && |
| 606 | + track_alias == 1) { |
| 607 | + saw_media_before_media_subscribe = true; |
| 608 | + } |
| 609 | + } |
| 610 | + }; |
| 611 | + |
| 612 | + MoqtSession session(transport, std::string(kTestTrackNamespace), false); |
| 613 | + |
| 614 | + auto status = session.connect(endpoint, tls); |
| 615 | + ok &= expect(status.ok, "expected delayed-subscriber session connect to succeed"); |
| 616 | + |
| 617 | + const PublishPlan materialized = |
| 618 | + materialize_publish_plan(make_span_backed_plan(DraftVersion::kDraft14), source_bytes); |
| 619 | + status = session.publish(materialized); |
| 620 | + ok &= expect(status.ok, "expected publish to succeed with delayed media subscriber"); |
| 621 | + ok &= expect(!saw_media_before_media_subscribe, |
| 622 | + "expected forward=0 to avoid sending media before the media subscriber arrives"); |
557 | 623 | } |
558 | 624 |
|
559 | 625 | { |
@@ -830,5 +896,27 @@ int main() { |
830 | 896 | ok &= expect(status.ok, "expected close to succeed"); |
831 | 897 | ok &= expect(close_transport.last_close_code == 7, "expected close code to propagate"); |
832 | 898 |
|
| 899 | + { |
| 900 | + MockTransport timeout_transport; |
| 901 | + timeout_transport.reads[0].push_back(encode_server_setup_message({ |
| 902 | + .draft = DraftVersion::kDraft14, |
| 903 | + .max_request_id = 8, |
| 904 | + })); |
| 905 | + queue_subscribe_requests(timeout_transport, |
| 906 | + DraftVersion::kDraft14, |
| 907 | + kTestTrackNamespace, |
| 908 | + {{2, "catalog"}, {4, "vide_1"}}); |
| 909 | + MoqtSession timeout_session( |
| 910 | + timeout_transport, std::string(kTestTrackNamespace), false, false, std::chrono::seconds(11)); |
| 911 | + status = timeout_session.connect(endpoint, tls); |
| 912 | + ok &= expect(status.ok, "expected custom-timeout session connect to succeed"); |
| 913 | + status = timeout_session.publish( |
| 914 | + materialize_publish_plan(make_span_backed_plan(DraftVersion::kDraft14), source_bytes)); |
| 915 | + ok &= expect(status.ok, "expected publish to succeed with custom subscriber timeout"); |
| 916 | + ok &= expect(!timeout_transport.read_timeouts.empty() && |
| 917 | + timeout_transport.read_timeouts.back() == std::chrono::seconds(11), |
| 918 | + "expected custom subscriber timeout to reach transport reads"); |
| 919 | + } |
| 920 | + |
833 | 921 | return ok ? 0 : 1; |
834 | 922 | } |
0 commit comments