blob: 408c7ccf3feb92eec4235e9234d7c61094cf3907 [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_subscribe_stream.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <variant>
#include "absl/base/nullability.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_framer.h"
#include "quiche/quic/moqt/moqt_key_value_pair.h"
#include "quiche/quic/moqt/moqt_live_publisher.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_names.h"
#include "quiche/quic/moqt/moqt_object_subscriber.h"
#include "quiche/quic/moqt/moqt_parser.h"
#include "quiche/quic/moqt/moqt_publisher.h"
#include "quiche/quic/moqt/moqt_session_callbacks.h"
#include "quiche/common/quiche_weak_ptr.h"
namespace moqt {
MoqtSubscribeRequestStream::MoqtSubscribeRequestStream(
MoqtFramer* absl_nonnull framer,
const MoqtControlMessageParser& message_parser, uint64_t request_id,
SessionErrorCallback session_error_callback, const FullTrackName& name,
SubscribeVisitor* absl_nonnull visitor, const MessageParameters& parameters,
LiveSubscriber::AddCallback add_callback,
LiveSubscriber::RemoveCallback remove_callback,
const quic::QuicClock* absl_nonnull clock,
quic::QuicAlarmFactory* absl_nonnull alarm_factory)
: MoqtBidiStreamBase(framer, message_parser,
std::move(session_error_callback)),
track_(std::make_unique<LiveSubscriber>(
MoqtSubscribe{request_id, name, parameters}, visitor, this)),
add_callback_(std::move(add_callback)),
remove_callback_(std::move(remove_callback)),
clock_(clock),
alarm_factory_(alarm_factory) {}
void MoqtSubscribeRequestStream::OnStreamBound() {
stream_parser()->set_allow_fin(true);
SendOrBufferMessageOrFatal(framer()->SerializeSubscribe(
MoqtSubscribe{track_->request_id(), track_->full_track_name(),
track_->const_parameters()}));
}
absl::Status MoqtSubscribeRequestStream::OnRawControlMessage(
const MoqtRawControlMessage& message) {
return ControlMessageDispatcher::DispatchControlMessage(
*this, message_parser(), message, "subscribe request");
}
absl::Status MoqtSubscribeRequestStream::OnControlMessage(
const MoqtSubscribeOk& message) {
if (message.request_id != track_->request_id()) {
return absl::InvalidArgumentError("SUBSCRIBE_OK request ID mismatch");
}
if (add_callback_ == nullptr) {
return absl::InvalidArgumentError(
"Multiple SUBSCRIBE_OK on the same stream");
}
track_->set_track_alias(message.track_alias);
if (!std::move(add_callback_)(track_.get())) {
add_callback_ = nullptr;
OnFatalError(absl::AlreadyExistsError("Track alias already exists"));
return absl::OkStatus();
}
add_callback_ = nullptr;
track_->OnObjectOrOk(SubscribeOkData(message.parameters, message.extensions));
return absl::OkStatus();
}
absl::Status MoqtSubscribeRequestStream::OnControlMessage(
const MoqtRequestOk& message) {
if (!track_->track_alias().has_value()) {
// Not yet established.
OnFatalError(
absl::InvalidArgumentError("REQUEST_OK received before SUBSCRIBE_OK"));
return absl::OkStatus();
}
auto status_or_params = PopParameters();
if (status_or_params.ok()) {
MessageParameters parameters = status_or_params.value();
// EXPIRES or LARGEST_OBJECT could be present in REQUEST_OK.
if (message.parameters.largest_object.has_value()) {
parameters.largest_object = message.parameters.largest_object;
}
if (message.parameters.expires.has_value()) {
parameters.expires = message.parameters.expires;
}
track_->Update(parameters);
}
return MoqtBidiStreamBase::OnControlMessage(message);
}
absl::Status MoqtSubscribeRequestStream::OnControlMessage(
const MoqtRequestError& message) {
MoqtRequestErrorInfo error_info{message.error_code, message.retry_interval,
message.reason_phrase};
if (track_->ErrorIsAllowed()) {
if (track_->visitor() != nullptr) {
track_->visitor()->OnReply(track_->full_track_name(), error_info);
}
Fin();
return absl::OkStatus();
}
// In response to REQUEST_UPDATE, utilize the ResponseCallback and do not
// update parameters.
return MoqtBidiStreamBase::OnControlMessage(message);
}
absl::Status MoqtSubscribeRequestStream::OnControlMessage(
const MoqtPublishDone& message) {
if (track_ == nullptr) {
// PUBLISH_DONE can be sent before the subscriber rejects the track.
return absl::OkStatus();
}
track_->OnPublishDone(message.stream_count, clock_, alarm_factory_);
return absl::OkStatus();
}
void MoqtSubscribeRequestStream::Detach() {
if (remove_callback_ != nullptr) {
LiveSubscriber::RemoveCallback remove_callback =
std::move(remove_callback_);
remove_callback_ = nullptr;
std::move(remove_callback)(track_.get());
}
track_ = nullptr;
}
MoqtSubscribeResponseStream::MoqtSubscribeResponseStream(
MoqtFramer* absl_nonnull framer,
const MoqtControlMessageParser& message_parser, uint64_t track_alias,
LivePublisher::AddCallback add_callback,
LivePublisher::RemoveCallback remove_callback,
SessionErrorCallback session_error_callback,
quiche::QuicheWeakPtr<SessionToPublisherInterface> session)
: MoqtBidiStreamBase(framer, message_parser,
std::move(session_error_callback)),
track_alias_(track_alias),
add_callback_(std::move(add_callback)),
remove_callback_(std::move(remove_callback)),
session_(std::move(session)) {}
absl::Status MoqtSubscribeResponseStream::OnRawControlMessage(
const MoqtRawControlMessage& message) {
return ControlMessageDispatcher::DispatchControlMessage(
*this, message_parser(), message, "subscribe response");
}
absl::Status MoqtSubscribeResponseStream::OnControlMessage(
const MoqtSubscribe& message) {
if (subscription_ != nullptr) {
return absl::InvalidArgumentError(
"SUBSCRIBE received on stream that already has a subscription");
}
QUIC_DLOG(INFO) << "Received a SUBSCRIBE for " << message.full_track_name;
if (session() == nullptr) {
return absl::OkStatus();
}
std::shared_ptr<MoqtTrackPublisher> track_publisher =
session()->GetTrackPublisher(message.full_track_name);
if (track_publisher == nullptr) {
QUIC_DLOG(INFO) << "SUBSCRIBE for " << message.full_track_name
<< " rejected by the application: does not exist";
return SendRequestError(message.request_id, RequestErrorCode::kDoesNotExist,
std::nullopt, "not found", /*fin=*/true);
}
subscription_ = std::make_unique<LivePublisher>(
*framer(), track_publisher, this, message.request_id, track_alias_,
message.parameters, session_, false);
if (add_callback_ != nullptr) {
bool result = std::move(add_callback_)(subscription_.get());
add_callback_ = nullptr;
if (!result) {
return SendRequestError(message.request_id,
RequestErrorCode::kDuplicateSubscription,
std::nullopt, "duplicate subscription",
/*fin=*/true);
}
}
// Don't add the publisher until we know it's successful.
track_publisher->AddObjectListener(subscription_.get());
return absl::OkStatus();
}
absl::Status MoqtSubscribeResponseStream::OnControlMessage(
const MoqtRequestUpdate& message) {
if (subscription_ == nullptr) {
QUICHE_BUG(INFO) << "Received REQUEST_UPDATE, no subscription state";
return SendRequestError(message.request_id,
RequestErrorCode::kInternalError, std::nullopt,
"no subscription", /*fin=*/true);
}
subscription_->Update(message.parameters);
return SendRequestOk(message.request_id, MessageParameters());
}
void MoqtSubscribeResponseStream::Detach() {
if (remove_callback_ != nullptr && subscription_ != nullptr) {
LivePublisher::RemoveCallback remove_callback = std::move(remove_callback_);
remove_callback_ = nullptr;
std::move(remove_callback)(subscription_.get());
}
if (subscription_ != nullptr) {
subscription_->ResetAllStreams();
subscription_ = nullptr;
}
}
} // namespace moqt