blob: 1e10548d87b8bd092125e8dfd17b37dab1e6c8e4 [file]
// Copyright (c) 2026 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_publish_stream.h"
#include <memory>
#include <optional>
#include <utility>
#include <variant>
#include "absl/base/nullability.h"
#include "absl/functional/overload.h"
#include "absl/status/status.h"
#include "quiche/quic/core/quic_alarm_factory.h"
#include "quiche/quic/core/quic_clock.h"
#include "quiche/quic/moqt/moqt_bidi_stream.h"
#include "quiche/quic/moqt/moqt_error.h"
#include "quiche/quic/moqt/moqt_fetch_task.h"
#include "quiche/quic/moqt/moqt_framer.h"
#include "quiche/quic/moqt/moqt_key_value_pair.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_parser.h"
#include "quiche/quic/moqt/moqt_session_callbacks.h"
#include "quiche/quic/moqt/moqt_subscription.h"
#include "quiche/quic/moqt/moqt_track.h"
namespace moqt {
MoqtPublishPublisherStream::MoqtPublishPublisherStream(
MoqtFramer* absl_nonnull framer,
const MoqtControlMessageParser& message_parser,
BidiStreamDeletedCallback stream_deleted_callback,
SessionErrorCallback session_error_callback,
MoqtResponseCallback response_callback)
: MoqtBidiStreamBase(framer, message_parser,
std::move(stream_deleted_callback),
std::move(session_error_callback)),
response_callback_(std::move(response_callback)) {}
MoqtPublishPublisherStream::~MoqtPublishPublisherStream() {}
void MoqtPublishPublisherStream::OnStreamBound() {
stream_parser()->set_allow_fin(true);
publisher_->parameters().largest_object =
publisher_->publisher().largest_location();
publisher_->parameters().expires = publisher_->publisher().expiration();
SendOrBufferMessageOrFatal(framer()->SerializePublish(MoqtPublish{
publisher_->request_id(), publisher_->publisher().GetTrackName(),
publisher_->track_alias(), publisher_->parameters(),
publisher_->publisher().extensions()}));
// Use the default group order.
publisher_->parameters().group_order =
publisher_->publisher().extensions().default_publisher_group_order();
}
absl::Status MoqtPublishPublisherStream::OnRawControlMessage(
const MoqtRawControlMessage& message) {
return ControlMessageDispatcher::DispatchControlMessage(
*this, message_parser(), message, "publish publisher");
}
// TODO(martinduke): When we allow the publisher to send REQUEST_UPDATE,
// REQUEST_OK and REQUEST_ERROR processing need to check the request ID.
absl::Status MoqtPublishPublisherStream::OnControlMessage(
const MoqtRequestOk& message) {
if (message.request_id != publisher_->request_id()) {
OnFatalError(absl::InvalidArgumentError(
"REQUEST_OK does not match PUBLISH request ID"));
return absl::OkStatus();
}
std::move(response_callback_)(message.parameters);
publisher_->Update(message.parameters);
// TODO(martinduke): Update() will not update group order because that is not
// allowed in REQUEST_UPDATE. PUBLISH_OK therefore needs to explicitly
// change the group order, but this would require reordering all streams by
// priority, and might create edge cases.
return absl::OkStatus();
}
absl::Status MoqtPublishPublisherStream::OnControlMessage(
const MoqtRequestError& message) {
if (message.request_id != publisher_->request_id()) {
OnFatalError(absl::InvalidArgumentError(
"REQUEST_OK does not match PUBLISH request ID"));
return absl::OkStatus();
}
std::move(response_callback_)(MoqtRequestErrorInfo{
message.error_code, message.retry_interval, message.reason_phrase});
return absl::OkStatus();
}
absl::Status MoqtPublishPublisherStream::OnControlMessage(
const MoqtRequestUpdate& message) {
MessageParameters in_parameters = message.parameters, out_parameters;
out_parameters.largest_object = publisher_->publisher().largest_location();
if (in_parameters.subscription_filter.has_value()) {
in_parameters.subscription_filter->OnLargestObject(
out_parameters.largest_object);
}
publisher_->Update(in_parameters);
CheckStatus(SendRequestOk(message.request_id, MessageParameters()));
return absl::OkStatus();
}
MoqtPublishSubscriberStream::MoqtPublishSubscriberStream(
MoqtFramer* absl_nonnull framer,
const MoqtControlMessageParser& message_parser,
const quic::QuicClock* absl_nonnull clock,
quic::QuicAlarmFactory* absl_nonnull alarm_factory,
SessionErrorCallback session_error_callback,
const MoqtIncomingPublishCallback* absl_nonnull incoming_publish_callback,
SubscribeRemoteTrack::SubscribeCallbacks callbacks)
: MoqtBidiStreamBase(
framer, message_parser,
/*stream_deleted_callback=*/+[]() {},
std::move(session_error_callback)),
clock_(clock),
alarm_factory_(alarm_factory),
incoming_publish_callback_(incoming_publish_callback),
callbacks_(std::move(callbacks)),
weak_ptr_factory_(this) {}
MoqtPublishSubscriberStream::~MoqtPublishSubscriberStream() {
in_destructor_ = true;
}
absl::Status MoqtPublishSubscriberStream::OnRawControlMessage(
const MoqtRawControlMessage& message) {
return ControlMessageDispatcher::DispatchControlMessage(
*this, message_parser(), message, "publish subscriber");
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtPublish& message) {
if (incoming_publish_callback_ == nullptr) {
// Two PUBLISH messages for the same stream.
return absl::InvalidArgumentError("Multiple PUBLISH on the same stream");
}
SubscribeVisitor* visitor = nullptr;
SubscribeRemoteTrack* existing_track =
std::move(callbacks_.query_name)(message.full_track_name);
callbacks_.query_name = nullptr;
if (existing_track != nullptr) {
// Track already exists.
if (!existing_track->ErrorIsAllowed()) {
// It's not a pending SUBSCRIBE; refuse this PUBLISH.
return SendRequestError(message.request_id,
RequestErrorCode::kDuplicateSubscription,
/*retry_interval=*/std::nullopt, "",
/*fin=*/true);
}
// It's a pending SUBSCRIBE. Transition it and accept the PUBLISH.
visitor = existing_track->ReleaseVisitor();
existing_track->Destroy();
} else {
// No existing SUBSCRIBE, get a new visitor from the application callback.
visitor = (*incoming_publish_callback_)(
message.full_track_name, message.parameters, message.extensions,
[weakptr = weak_ptr_factory_.Create(), request_id = message.request_id](
const std::variant<MessageParameters, MoqtRequestErrorInfo>
response) {
MoqtPublishSubscriberStream* stream = weakptr.GetIfAvailable();
if (stream == nullptr) {
return;
}
std::visit(
absl::Overload{[&](const MessageParameters& parameters) {
stream->subscriber_->Update(parameters);
stream->CheckStatus(stream->SendRequestOk(
request_id, parameters));
},
[&](const MoqtRequestErrorInfo& error_info) {
stream->CheckStatus(stream->SendRequestError(
request_id, error_info));
}},
response);
});
}
incoming_publish_callback_ = nullptr;
if (visitor == nullptr) {
CheckStatus(SendRequestError(message.request_id,
RequestErrorCode::kUninterested,
/*retry_interval=*/std::nullopt, "",
/*fin=*/true));
return absl::OkStatus();
}
subscriber_ = std::make_unique<SubscribeRemoteTrack>(
message, visitor,
[this]() {
if (!in_destructor_) {
subscriber_.reset();
stream()->ResetWithUserCode(kResetCodeCancelled);
}
},
std::move(callbacks_));
bool success = subscriber_->set_track_alias(message.track_alias);
if (!success) {
OnFatalError(absl::AlreadyExistsError(""));
}
return absl::OkStatus();
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtRequestUpdate& message) {
subscriber_->Update(message.parameters);
CheckStatus(SendRequestOk(message.request_id, MessageParameters()));
return absl::OkStatus();
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtRequestOk& message) {
// TODO(martinduke): Implement REQUEST_UPDATE.
return absl::OkStatus();
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtRequestError& message) {
// TODO(martinduke): Implement REQUEST_UPDATE.
return absl::OkStatus();
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtPublishDone& message) {
if (subscriber_ == nullptr) {
// PUBLISH_DONE can be sent before the subscriber rejects the track.
return absl::OkStatus();
}
subscriber_->OnPublishDone(message.stream_count, clock_, alarm_factory_);
return absl::OkStatus();
}
} // namespace moqt