blob: dd7c36e62689c38f8cc847e5e7e641584a858e26 [file] [edit]
// 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,
SubscriptionPublisher::RemoveCallback stream_deleted_callback,
SessionErrorCallback session_error_callback,
MoqtResponseCallback response_callback)
: MoqtBidiStreamBase(framer, message_parser,
std::move(session_error_callback)),
response_callback_(std::move(response_callback)),
stream_deleted_callback_(std::move(stream_deleted_callback)) {}
MoqtPublishPublisherStream::~MoqtPublishPublisherStream() { Detach(); }
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::AddCallback add_callback,
SubscribeRemoteTrack::RemoveCallback remove_callback)
: MoqtBidiStreamBase(framer, message_parser,
std::move(session_error_callback)),
clock_(clock),
alarm_factory_(alarm_factory),
incoming_publish_callback_(incoming_publish_callback),
add_callback_(std::move(add_callback)),
remove_callback_(std::move(remove_callback)),
weak_ptr_factory_(this) {}
absl::Status MoqtPublishSubscriberStream::OnRawControlMessage(
const MoqtRawControlMessage& message) {
return ControlMessageDispatcher::DispatchControlMessage(
*this, message_parser(), message, "publish subscriber");
}
absl::Status MoqtPublishSubscriberStream::OnControlMessage(
const MoqtPublish& message) {
if (add_callback_ == nullptr) {
// Two PUBLISH messages for the same stream.
return absl::InvalidArgumentError("Multiple PUBLISH on the same stream");
}
subscriber_ = std::make_unique<SubscribeRemoteTrack>(message, nullptr, this);
if (!std::move(add_callback_)(subscriber_.get())) {
add_callback_ = nullptr;
return SendRequestError(message.request_id,
RequestErrorCode::kDuplicateSubscription,
/*retry_interval=*/std::nullopt, "",
/*fin=*/true);
}
add_callback_ = nullptr;
if (subscriber_->visitor() == nullptr) {
// There was no existing SUBSCRIBE, so invoke the callback.
subscriber_->set_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, /*fin=*/false));
},
[&](const MoqtRequestErrorInfo& error_info) {
stream->CheckStatus(stream->SendRequestError(
request_id, error_info, /*fin=*/true));
}},
response);
}));
}
incoming_publish_callback_ = nullptr;
if (subscriber_->visitor() == nullptr) {
// The application doesn't care.
CheckStatus(SendRequestError(message.request_id,
RequestErrorCode::kUninterested,
/*retry_interval=*/std::nullopt, "",
/*fin=*/true));
return absl::OkStatus();
}
// Notify the visitor.
subscriber_->OnObjectOrOk(
SubscribeOkData{message.parameters, message.extensions});
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