@@ -955,6 +955,115 @@ TEST_F(DataTrackE2ETest, PreservesUserTimestampEndToEnd) {
955955 EXPECT_EQ (frame.user_timestamp .value (), sent_timestamp);
956956}
957957
958+ TEST_F (DataTrackE2ETest, RejectsNullBorrowedPayloadWithNonZeroSize) {
959+ const auto track_name = makeTrackName (" null_borrowed_payload" );
960+ auto rooms = testRooms (1 );
961+ auto local_track = requirePublishedTrack (rooms[0 ]->localParticipant (), track_name);
962+
963+ const auto push_result = local_track->tryPush (static_cast <const std::uint8_t *>(nullptr ), 4 );
964+ ASSERT_FALSE (push_result);
965+ EXPECT_EQ (push_result.error ().code , LocalDataTrackTryPushErrorCode::INTERNAL );
966+ EXPECT_FALSE (push_result.error ().message .empty ());
967+
968+ local_track->unpublishDataTrack ();
969+ }
970+
971+ TEST_F (DataTrackE2ETest, AcceptsEmptyPayload) {
972+ const auto track_name = makeTrackName (" empty_borrowed_payload" );
973+ auto rooms = testRooms (1 );
974+ auto local_track = requirePublishedTrack (rooms[0 ]->localParticipant (), track_name);
975+
976+ const std::uint8_t payload = 0 ;
977+ EXPECT_TRUE (local_track->tryPush (&payload, 0 ));
978+ EXPECT_TRUE (local_track->tryPush (nullptr , 0 ));
979+ EXPECT_TRUE (local_track->tryPush (std::vector<std::uint8_t >{}));
980+
981+ local_track->unpublishDataTrack ();
982+ }
983+
984+ TEST_F (DataTrackE2ETest, CopiesBorrowedPayloadBeforeTryPushReturns) {
985+ const auto track_name = makeTrackName (" borrowed_payload" );
986+ const auto sent_timestamp = getTimestampUs ();
987+ constexpr std::uint8_t kOriginal = 0xA5 ;
988+ constexpr std::uint8_t kMutated = 0x5A ;
989+ constexpr std::size_t kPayloadSize = 64 ;
990+
991+ DataTrackPublishedDelegate subscriber_delegate;
992+ std::vector<TestRoomConnectionOptions> room_configs (2 );
993+ room_configs[1 ].delegate = &subscriber_delegate;
994+
995+ auto rooms = testRooms (room_configs);
996+ auto & publisher_room = rooms[0 ];
997+
998+ auto publish_result = lockLocalParticipant (*publisher_room)->publishDataTrack (track_name);
999+ if (!publish_result) {
1000+ FAIL () << describeDataTrackError (publish_result.error ());
1001+ }
1002+ auto local_track = publish_result.value ();
1003+ ASSERT_TRUE (local_track->isPublished ());
1004+
1005+ auto remote_track = subscriber_delegate.waitForTrack (kTrackWaitTimeout );
1006+ ASSERT_NE (remote_track, nullptr ) << " Timed out waiting for remote data track" ;
1007+
1008+ auto subscribe_result = remote_track->subscribe ();
1009+ if (!subscribe_result) {
1010+ FAIL () << describeDataTrackError (subscribe_result.error ());
1011+ }
1012+ auto subscription = subscribe_result.value ();
1013+
1014+ std::promise<DataTrackFrame> frame_promise;
1015+ auto frame_future = frame_promise.get_future ();
1016+ std::thread reader ([&]() {
1017+ try {
1018+ DataTrackFrame frame;
1019+ if (!subscription->read (frame)) {
1020+ throw std::runtime_error (" Subscription ended before borrowed-payload frame arrived" );
1021+ }
1022+ frame_promise.set_value (std::move (frame));
1023+ } catch (...) {
1024+ frame_promise.set_exception (std::current_exception ());
1025+ }
1026+ });
1027+
1028+ bool pushed = false ;
1029+ for (int attempt = 0 ; attempt < kTimestampFrameAttempts ; ++attempt) {
1030+ std::array<std::uint8_t , kPayloadSize > payload{};
1031+ payload.fill (kOriginal );
1032+ auto push_result = local_track->tryPush (payload.data (), payload.size (), sent_timestamp);
1033+ payload.fill (kMutated );
1034+ pushed = static_cast <bool >(push_result) || pushed;
1035+ if (frame_future.wait_for (25ms) == std::future_status::ready) {
1036+ break ;
1037+ }
1038+ }
1039+ const auto frame_status = frame_future.wait_for (5s);
1040+
1041+ if (frame_status != std::future_status::ready) {
1042+ subscription->close ();
1043+ }
1044+
1045+ subscription->close ();
1046+ reader.join ();
1047+ local_track->unpublishDataTrack ();
1048+
1049+ ASSERT_TRUE (pushed) << " Failed to push borrowed data frame" ;
1050+ ASSERT_EQ (frame_status, std::future_status::ready) << " Timed out waiting for borrowed-payload frame" ;
1051+
1052+ DataTrackFrame frame;
1053+ try {
1054+ frame = frame_future.get ();
1055+ } catch (const std::exception& e) {
1056+ FAIL () << e.what ();
1057+ }
1058+
1059+ ASSERT_EQ (frame.payload .size (), kPayloadSize );
1060+ EXPECT_TRUE (std::all_of (frame.payload .begin (), frame.payload .end (), [expected = kOriginal ](std::uint8_t byte) {
1061+ return byte == expected;
1062+ })) << " Received payload reflects caller mutation after tryPush returned" ;
1063+ ASSERT_TRUE (frame.user_timestamp .has_value ());
1064+ EXPECT_EQ (frame.user_timestamp .value (), sent_timestamp);
1065+ }
1066+
9581067TEST_F (DataTrackE2ETest, PublishesAndReceivesEncryptedFramesEndToEnd) {
9591068 runEncryptedDataTrackRoundTrip (kDefaultKeyDerivationFunction , " e2ee_transport" );
9601069}
0 commit comments