blob: 103e0f9706c871ed1780955173d88a6d2c14833d [file] [log] [blame]
// 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_NAMESPACE_TREE_H_
#define QUICHE_QUIC_MOQT_NAMESPACE_TREE_H_
#include <cstddef>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/types/span.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 NamespaceTree {
public:
NamespaceTree() = default;
~NamespaceTree() = default;
// Returns false if the namespace can't be added because it intersects with an
// existing namespace.
bool AddNamespace(const TrackNamespace& track_namespace) {
if (root_.children.empty()) {
AddToTree(track_namespace.tuple(), root_);
return true;
}
return TraverseTree(track_namespace.tuple(), root_);
}
// Called when UNSUBSCRIBE_NAMESPACE is received.
void RemoveNamespace(const TrackNamespace& track_namespace) {
DeleteUniqueBranches(track_namespace.tuple(), root_);
}
private:
struct Node {
absl::flat_hash_map<std::string, struct Node> children;
};
// Recursively add new elements of the tuple to the tree. |start_index| is the
// element of |tuple| that is added directly to |parent_node|.
void AddToTree(absl::Span<const std::string> tuple, Node& parent_node) {
if (tuple.empty()) {
return;
}
auto [it, success] = parent_node.children.emplace(tuple[0], Node());
AddToTree(tuple.subspan(1), it->second);
}
bool TraverseTree(absl::Span<const std::string> tuple, Node& node) {
if (node.children.empty()) {
// The new namespace would be a child of an existing namespace.
return false;
}
if (tuple.empty()) {
// The new namespace would be a parent of an existing namespace.
return false;
}
auto it = node.children.find(tuple[0]);
if (it == node.children.end()) {
// The new namespace would be a cousin of an existing namespace. This is
// allowed.
AddToTree(tuple, node);
return true;
}
return TraverseTree(tuple.subspan(1), it->second);
}
// This recursive function finds the deepest leaf node for this namespace. It
// then keeps deleting towards the root until it finds a parent node with
// multiple children.
// Returns false if there are other children of parent_node, so that it's not
// safe to keep deleting.
bool DeleteUniqueBranches(absl::Span<const std::string> tuple,
Node& parent_node) {
if (tuple.empty()) {
// We've reached the end of the namespace, it's unique if there are no
// children.
return parent_node.children.empty();
}
if (parent_node.children.empty()) {
// Ran out of leaves too early. The namespace is not present.
return false;
}
auto it = parent_node.children.find(tuple[0]);
if (it == parent_node.children.end()) {
// The namespace was not present.
return false;
}
// Go to the next leaf node.
if (!DeleteUniqueBranches(tuple.subspan(1), it->second)) {
// Do no more deletion.
return false;
}
parent_node.children.erase(it);
// If there other children at this level, stop deleting.
return parent_node.children.empty();
}
Node root_; // Not a legal namespace. It's the root of the tree.
};
} // namespace moqt
#endif // QUICHE_QUIC_MOQT_NAMESPACE_TREE_H_