blob: b68f3826cb37a4d64300a25f77ded8480b77a3e2 [file] [log] [blame] [edit]
// Copyright 2025 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.
#ifndef QUICHE_QUIC_MOQT_SESSION_NAMESPACE_TREE_H_
#define QUICHE_QUIC_MOQT_SESSION_NAMESPACE_TREE_H_
#include <cstdint>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "quiche/quic/moqt/moqt_messages.h"
namespace moqt {
// Publishers MUST respond with an error if a SUBSCRIBE_NAMESPACE arrives that
// in any way intersects with an existing SUBSCRIBE_NAMESPACE. This requires a
// fairly complex data structure where each part of the tuple is a node. If a
// node has no children, it indicates a complete namespace, and there can be no
// other complete namespaces as direct ancestors or descendants.
// For example, if a/b/c and a/b/d are in the tree, then a/b/e is allowed, but
// a/b and a/b/c/d would not be.
class SessionNamespaceTree {
public:
SessionNamespaceTree() = default;
~SessionNamespaceTree() {}
// Returns false if the namespace was not subscribed.
bool SubscribeNamespace(const TrackNamespace& track_namespace) {
if (prohibited_namespaces_.contains(track_namespace)) {
return false;
}
TrackNamespace higher_namespace = track_namespace;
do {
if (subscribed_namespaces_.contains(higher_namespace)) {
return false;
}
} while (higher_namespace.PopElement());
subscribed_namespaces_.insert(track_namespace);
// Add a reference to every higher namespace to block future subscriptions.
higher_namespace = track_namespace;
while (higher_namespace.PopElement()) {
++prohibited_namespaces_[higher_namespace];
}
return true;
}
void UnsubscribeNamespace(const TrackNamespace& track_namespace) {
if (subscribed_namespaces_.erase(track_namespace) == 0) {
return;
}
// Delete one ref from prohibited_namespaces_.
TrackNamespace higher_namespace = track_namespace;
while (higher_namespace.PopElement()) {
auto it2 = prohibited_namespaces_.find(higher_namespace);
if (it2 == prohibited_namespaces_.end()) {
continue;
}
if (it2->second == 1) {
prohibited_namespaces_.erase(it2);
} else {
--it2->second;
}
}
}
// Used only when the SessionNamespaceTree is being destroyed.
const absl::flat_hash_set<TrackNamespace>& GetSubscribedNamespaces() const {
return subscribed_namespaces_;
}
protected:
uint64_t NumSubscriptions() const { return subscribed_namespaces_.size(); }
private:
absl::flat_hash_set<TrackNamespace> subscribed_namespaces_;
// Namespaces that cannot be subscribed to because they intersect with an
// existing subscription. The value is a ref count.
absl::flat_hash_map<TrackNamespace, int> prohibited_namespaces_;
};
} // namespace moqt
#endif // QUICHE_QUIC_MOQT_SESSION_NAMESPACE_TREE_H_