Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ set (CMAKE_POLICY_VERSION_MINIMUM 3.5)
### Define the project name and version
### Version will be added to version output of apps and library
project(laps
VERSION 0.18.23
VERSION 0.18.31
DESCRIPTION "Latency Aware Publish/Subscribe"
LANGUAGES CXX)

Expand Down Expand Up @@ -57,10 +57,11 @@ endif()
###
add_subdirectory(dependencies)

# quicr/log.h picks between std::format and fmt on this definition, which libquicr only defines for its own
# sources. The relay logs through quicr::Logger, so it has to make the same choice libquicr did.
if(HAS_STD_VFORMAT)
add_compile_definitions(QUICR_HAVE_STD_FORMAT)
# quicr/utilities/format.h selects std::format or {fmt} with QUICR_HAS_STD_FORMAT.
# libquicr only adds that definition under its own directory, so relay sources that
# include quicr headers have to repeat the same choice.
if(QUICR_HAS_STD_FORMAT)
add_compile_definitions(QUICR_HAS_STD_FORMAT)
endif()

# spdlog compiles a bundled copy of fmt by default. libquicr also links fmt::fmt
Expand Down
2 changes: 1 addition & 1 deletion dependencies/libquicr
Submodule libquicr updated 97 files
+3 −3 .github/workflows/macos.yml
+0 −47 .github/workflows/mbedtls.yml
+36 −0 .github/workflows/reuse.yaml
+1 −0 .github/workflows/sanitizers.yml
+194 −0 .github/workflows/tls-backends.yml
+80 −47 .github/workflows/windows.yml
+1 −0 .gitignore
+6 −0 .pre-commit-config.yaml
+33 −38 CMakeLists.txt
+14 −1 Makefile
+196 −121 README.md
+4 −7 REUSE.toml
+3 −0 benchmark/.clang-tidy
+3 −0 benchmark/extensions.cpp
+3 −0 benchmark/priority_queue.cpp
+0 −55 build.Dockerfile
+3 −10 c-bridge/CMakeLists.txt
+3 −2 c-bridge/README.md
+1 −0 c-bridge/include/quicr/quicr_bridge.h
+4 −1 c-bridge/quicr-bridge.pc.in
+9 −3 c-bridge/src/quicr_bridge.cpp
+0 −4 certs/LICENSE
+0 −19 certs/badcert.pem
+0 −19 certs/cert.pem
+ − certs/esni-rr.bin
+0 −5 certs/esni-secp256r1.key
+0 −28 certs/key.pem
+0 −20 certs/test-ca.crt
+31 −0 cmake/CheckThreadSafetyAnnotations.cmake
+3 −0 cmake/Lint.cmake
+12 −60 cmake/QuicrDependencies.cmake
+34 −0 cmake/QuicrFormat.cmake
+3 −0 cmake/version.h.in
+16 −7 docs/api-guide.md
+17 −0 examples/README.md
+3 −0 examples/dependencies/.clang-format-ignore
+1 −1 examples/qclient/CMakeLists.txt
+54 −0 examples/qclient/README.md
+79 −37 examples/qclient/client.cpp
+3 −18 examples/qclient/helper_functions.h
+21 −20 include/quicr/attributes.h
+18 −32 include/quicr/connection.h
+1 −0 include/quicr/containers/safe_queue.h
+25 −0 include/quicr/containers/stream_buffer.h
+0 −6 include/quicr/handlers/fetch_track_handler.h
+59 −0 include/quicr/handlers/forwarding_subscribe_track_handler.h
+4 −5 include/quicr/handlers/joining_fetch_handler.h
+1 −1 include/quicr/handlers/publish_fetch_handler.h
+43 −18 include/quicr/handlers/publish_track_handler.h
+3 −0 include/quicr/handlers/subscribe_namespace_handler.h
+41 −52 include/quicr/handlers/subscribe_track_handler.h
+57 −0 include/quicr/handlers/track_status_handler.h
+3 −0 include/quicr/log.h
+3 −3 include/quicr/messages/message_serialisation.h
+9 −8 include/quicr/reply.h
+125 −49 include/quicr/session.h
+47 −62 include/quicr/session_callbacks.h
+6 −44 include/quicr/transport.h
+3 −0 include/quicr/utilities/expected.h
+13 −6 include/quicr/utilities/format.h
+7 −1 include/quicr/utilities/thread_safety.h
+66 −0 package-lock.cmake
+10 −2 src/CMakeLists.txt
+15 −16 src/connection.cpp
+3 −0 src/ctrl_message_types.cpp
+0 −56 src/fetch_track_handler.cpp
+4 −43 src/joining_fetch_handler.cpp
+3 −0 src/log.cpp
+9 −9 src/message_serialisation.cpp
+7 −10 src/picoquic_connection.h
+5 −8 src/publish_fetch_handler.cpp
+5 −2 src/publish_namespace_handler.cpp
+21 −13 src/publish_track_handler.cpp
+1,097 −783 src/session.cpp
+14 −33 src/session_callbacks.cpp
+16 −10 src/session_manager.cpp
+148 −2 src/stream.h
+3 −0 src/subscribe_namespace_handler.cpp
+13 −126 src/subscribe_track_handler.cpp
+3 −0 src/track_handler.cpp
+79 −0 src/track_status_handler.cpp
+98 −116 src/transport_picoquic.cpp
+8 −16 src/transport_picoquic.h
+7 −1 test/CMakeLists.txt
+18 −4 test/integration_test/CMakeLists.txt
+772 −88 test/integration_test/integration_test.cpp
+13 −5 test/integration_test/test_client.cpp
+26 −4 test/integration_test/test_client.h
+39 −12 test/integration_test/test_server.cpp
+60 −38 test/integration_test/test_server.h
+4 −4 test/moq_data_messages.cpp
+22 −4 test/picoquic_connection.cpp
+3 −0 test/priority_queue.cpp
+110 −0 test/reply.cpp
+19 −83 test/track_handlers.cpp
+3 −0 test/track_namespace.cpp
+0 −9 vcpkg.json
7 changes: 5 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ if(CLANG_TIDY_EXE)
set_target_properties(quicr PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
endif()


add_executable(lapsRelay
main.cc
config.cc
Expand All @@ -30,7 +29,11 @@ add_executable(lapsRelay
)

target_link_libraries(lapsRelay PRIVATE quicr spdlog::spdlog)
target_include_directories(lapsRelay PRIVATE ${PROJECT_SOURCE_DIR}/dependencies/oss )
target_include_directories(lapsRelay PRIVATE
${PROJECT_SOURCE_DIR}/dependencies/oss
# Peer sessions read quicr::Stream directly. That type is defined for the library, not installed
# with the public headers.
${PROJECT_SOURCE_DIR}/dependencies/libquicr/src)

target_compile_options(lapsRelay
PRIVATE
Expand Down
393 changes: 283 additions & 110 deletions src/client_manager.cc

Large diffs are not rendered by default.

52 changes: 38 additions & 14 deletions src/client_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,52 +164,52 @@ namespace laps {
void MetricsSampled(const std::shared_ptr<quicr::Session>& session,
const quicr::ConnectionMetrics& metrics) override;

quicr::Reply<void, quicr::ErrorCode> ClientSetupReceived(
quicr::Expected<void, quicr::Error<quicr::ErrorCode>> SetupReceived(
const std::shared_ptr<quicr::Session>& session,
const quicr::ClientSetupAttributes& client_setup_attributes) override;
const quicr::SetupAttributes& setup_attributes) override;

quicr::Reply<std::vector<quicr::TrackNamespace>, quicr::RequestErrorCode> SubscribeTracksReceived(
const std::shared_ptr<quicr::Session>& session,
const quicr::TrackNamespace& prefix_namespace,
const quicr::SubscribeNamespaceAttributes& attributes) override;

quicr::Reply<void, quicr::ErrorCode> UnsubscribeNamespaceReceived(
quicr::Reply<std::vector<quicr::TrackNamespace>, quicr::RequestErrorCode> SubscribeNamespaceReceived(
const std::shared_ptr<quicr::Session>& session,
const quicr::TrackNamespace& prefix_namespace) override;
const quicr::TrackNamespace& prefix_namespace,
const quicr::SubscribeNamespaceAttributes& attributes) override;

void UnsubscribeNamespaceReceived(const std::shared_ptr<quicr::Session>& session,
const quicr::TrackNamespace& prefix_namespace) override;

quicr::Reply<void, quicr::PublishNamespaceErrorCode> PublishNamespaceReceived(
const std::shared_ptr<quicr::Session>& session,
const quicr::TrackNamespace& track_namespace,
const quicr::PublishNamespaceAttributes& attributes) override;

quicr::Reply<void, quicr::PublishNamespaceErrorCode> PublishNamespaceDoneReceived(
const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id) override;
void PublishNamespaceDoneReceived(const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id) override;

quicr::Reply<const quicr::PublishResponse, quicr::PublishErrorCode> PublishReceived(
const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id,
const quicr::PublishAttributes& publish_attributes,
std::weak_ptr<quicr::SubscribeNamespaceHandler> sub_ns_handler) override;

quicr::Reply<void, quicr::ErrorCode> PublishDoneReceived(const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id) override;
void PublishDoneReceived(const std::shared_ptr<quicr::Session>& session, std::uint64_t request_id) override;

quicr::Reply<quicr::RequestResponse, quicr::RequestErrorCode> SubscribeReceived(
quicr::Reply<quicr::SubscribeResponse, quicr::RequestErrorCode> SubscribeReceived(
const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id,
const quicr::FullTrackName& track_full_name,
const quicr::SubscribeAttributes& subscribe_attributes) override;

quicr::Reply<void, quicr::ErrorCode> UnsubscribeReceived(const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id) override;
void UnsubscribeReceived(const std::shared_ptr<quicr::Session>& session, std::uint64_t request_id) override;

quicr::Reply<void, quicr::ErrorCode> NewGroupRequested(const quicr::FullTrackName& track_full_name,
std::uint64_t group_id) override;

quicr::Reply<quicr::RequestResponse, quicr::RequestErrorCode> TrackStatusReceived(
quicr::Reply<quicr::TrackStatusResponse, quicr::RequestErrorCode> TrackStatusReceived(
const std::shared_ptr<quicr::Session>& session,
std::uint64_t request_id,
const quicr::FullTrackName& track_full_name) override;

quicr::Reply<const quicr::FetchResponse, quicr::FetchErrorCode> StandaloneFetchReceived(
Expand Down Expand Up @@ -259,6 +259,27 @@ namespace laps {
*/
void FanoutPublishNamespaceDone(const quicr::TrackNamespace& track_namespace);

/**
* @brief Send NAMESPACE or NAMESPACE_DONE to subscribe-namespace subscribers whose prefix matches
*
* @param track_namespace Fully qualified namespace that became active or ended
* @param exclude_connection Publishing connection. Skipped unless self-forwarding is allowed
* @param done True sends NAMESPACE_DONE, false sends NAMESPACE
*/
void NotifySubscribeNamespaces(const quicr::TrackNamespace& track_namespace,
std::uint64_t exclude_connection,
bool done);

/**
* @brief True when some publisher still has this namespace announced or a track in it
*/
bool NamespaceStillPublished(const quicr::TrackNamespace& track_namespace) const;

/**
* @brief Drop one subscribe-namespace subscription and any NAMESPACE already sent for it
*/
void DropSubscribeNamespace(std::uint64_t connection_handle, const quicr::TrackNamespace& prefix);

quicr::Reply<const quicr::PublishResponse, quicr::PublishErrorCode> PublishReceivedInternal(
std::uint64_t connection_handle,
uint64_t request_id,
Expand Down Expand Up @@ -298,6 +319,9 @@ namespace laps {
std::map<std::pair<quicr::TrackNamespace, std::uint64_t>, std::shared_ptr<quicr::PublishNamespaceHandler>>
fanout_namespaces_;

/// Namespaces already reported with NAMESPACE, keyed by namespace and subscriber connection
std::set<std::pair<quicr::TrackNamespace, std::uint64_t>> notified_namespaces_;

/**
* @brief Map of atomic bools to mark if a fetch thread should be interrupted.
*/
Expand Down
64 changes: 7 additions & 57 deletions src/fetch_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,64 +18,14 @@ namespace laps {
{
}

void FetchTrackHandler::StreamDataRecv(uint64_t stream_id, quicr::InitialStreamData&& initial_buffer)
void FetchTrackHandler::ObjectReceived(
const quicr::ObjectHeaders& object_headers,
quicr::BytesSpan data,
[[maybe_unused]] std::optional<quicr::messages::StreamHeaderProperties> stream_mode)
{
subscribe_track_metrics_.bytes_received += initial_buffer.buffer.Size();

auto [stream_it, inserted] =
streams_.try_emplace(stream_id, StreamContext{ .buffer = std::move(initial_buffer.buffer) });
if (!inserted) {
SPDLOG_ERROR("StreamDataRecv got new stream for existing Stream ID {}", stream_id);
return;
}

stream_it->second.buffer.InitAny<quicr::messages::FetchHeader>();
TryForwardInitialStreamData(stream_id, stream_it->second);
}

void FetchTrackHandler::StreamDataRecv(uint64_t stream_id, std::shared_ptr<const std::vector<uint8_t>> data)
{
subscribe_track_metrics_.bytes_received += data->size();

if (initial_stream_data_forwarded_) {
publish_fetch_handler_->ForwardPublishedData(false, 0, 0, std::move(data));
return;
}

const auto stream_it = streams_.find(stream_id);
if (stream_it == streams_.end()) {
SPDLOG_ERROR("StreamDataRecv had no stream for expected Stream ID {}", stream_id);
return;
}

stream_it->second.buffer.Push(*data);
TryForwardInitialStreamData(stream_id, stream_it->second);
}

void FetchTrackHandler::TryForwardInitialStreamData(uint64_t stream_id, StreamContext& stream)
{
auto& f_hdr = stream.buffer.GetAny<quicr::messages::FetchHeader>();
if (!(stream.buffer >> f_hdr)) {
return;
}

SPDLOG_DEBUG("Fetch header added in rid: {} out rid: {} remaining data size: {}",
f_hdr.request_id,
*publish_fetch_handler_->GetRequestId(),
stream.buffer.Size());

f_hdr.request_id = *publish_fetch_handler_->GetRequestId();
auto bytes = std::make_shared<quicr::Bytes>();
*bytes << f_hdr;

const auto remaining_data = stream.buffer.Data();
if (!remaining_data.empty()) {
bytes->insert(bytes->end(), remaining_data.begin(), remaining_data.end());
}

publish_fetch_handler_->ForwardPublishedData(true, 0, 0, std::move(bytes));
initial_stream_data_forwarded_ = true;
streams_.erase(stream_id);
// The session has already parsed the fetch header, so the outbound fetch writes its own, using this
// handler's request id.
publish_fetch_handler_->PublishObject(object_headers, data);
}

void FetchTrackHandler::StatusChanged(Status status)
Expand Down
8 changes: 3 additions & 5 deletions src/fetch_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,11 @@ namespace laps {
}

void StatusChanged(Status status) override;
void StreamDataRecv(uint64_t stream_id, quicr::InitialStreamData&& initial_buffer) override;
void StreamDataRecv(uint64_t stream_id, std::shared_ptr<const std::vector<uint8_t>> data) override;
void ObjectReceived(const quicr::ObjectHeaders& object_headers,
quicr::BytesSpan data,
std::optional<quicr::messages::StreamHeaderProperties> stream_mode = std::nullopt) override;

private:
void TryForwardInitialStreamData(uint64_t stream_id, StreamContext& stream);

bool initial_stream_data_forwarded_{ false };
std::shared_ptr<quicr::PublishFetchHandler> publish_fetch_handler_;
};
} // namespace laps
36 changes: 33 additions & 3 deletions src/peering/info_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
#include "peer_session.h"
#include "peering/messages/subscribe_info.h"

#include <functional>
#include <map>
#include <quicr/messages/message_serialisation.h>
#include <set>
#include <unordered_map>

namespace laps::peering {

Expand Down Expand Up @@ -148,17 +150,45 @@ namespace laps::peering {

std::map<std::uint64_t, std::map<NodeIdValueType, SubscribeInfo>> subscribes_;

/**
* @brief Identity of one egress stream held by a forwarding entry
*
* @details Client entries use `a` as the group id and `b` as the subgroup id. Peer entries use `a` as
* the ingress QUIC stream id and leave `b` at 0. Those two maps do not share entries.
*/
struct StreamKey
{
uint64_t a{ 0 };
uint64_t b{ 0 };

friend bool operator==(const StreamKey& lhs, const StreamKey& rhs) noexcept
{
return lhs.a == rhs.a && lhs.b == rhs.b;
}
};

struct StreamKeyHash
{
std::size_t operator()(const StreamKey& key) const noexcept
{
const auto h1 = std::hash<uint64_t>{}(key.a);
const auto h2 = std::hash<uint64_t>{}(key.b);
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
}
};

struct FibEntry
{
uint64_t update_ref{ 0 }; ///< Random reference number to detect if entry was updated or not

/**
* @brief Egress stream for each ingress stream, keyed by ingress stream ID
* @brief Egress stream for each forwarded flow
*
* @details The transport hands out stream handles rather than IDs, and holding one keeps the
* stream usable for as long as objects are being forwarded onto it.
* stream usable for as long as objects are being forwarded onto it. See StreamKey for how
* each map identifies a flow.
*/
std::unordered_map<uint64_t, std::shared_ptr<quicr::Stream>> streams;
std::unordered_map<StreamKey, std::shared_ptr<quicr::Stream>, StreamKeyHash> streams;

SubscribeNodeSetId out_sns_id; ///< Egress SNS ID
decltype(nodes_best_)::mapped_type peer_session;
Expand Down
Loading
Loading