From 02a86c7f29b3959a8a676932020cff976c2242c0 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 09:51:29 -0700 Subject: [PATCH 01/71] bring in encode/decode and support Announce via StreamBuffer --- include/quicr/moq_messages.h | 295 ++++++++++++++++ src/CMakeLists.txt | 1 + src/moq_messages.cpp | 643 +++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 4 +- test/moq_messages.cpp | 38 +++ 5 files changed, 980 insertions(+), 1 deletion(-) create mode 100644 include/quicr/moq_messages.h create mode 100644 src/moq_messages.cpp create mode 100644 test/moq_messages.cpp diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h new file mode 100644 index 00000000..1c33bebd --- /dev/null +++ b/include/quicr/moq_messages.h @@ -0,0 +1,295 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace quicr::messages { + +using Version = uintVar_t; +using TrackNamespace = quicr::bytes; +using TrackName = quicr::bytes; +using ErrorCode = uintVar_t; +using StatusCode = uintVar_t; +using ReasonPhrase = quicr::bytes; +using GroupId = uintVar_t; +using ObjectId = uintVar_t; +using ObjectPriority = uintVar_t; +using SubscribeId = uintVar_t; +using TrackAlias = uintVar_t; +using ParamType = uintVar_t; + +// Ref: https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-messages +constexpr uint8_t MESSAGE_TYPE_OBJECT_STREAM = 0x0; +constexpr uint8_t MESSAGE_TYPE_OBJECT_DATAGRAM = 0x1; +constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE = 0x3; +constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_OK = 0x4; +constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_ERROR = 0x5; +constexpr uint8_t MESSAGE_TYPE_ANNOUNCE = 0x6; +constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_OK = 0x7; +constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_ERROR = 0x8; +constexpr uint8_t MESSAGE_TYPE_UNANNOUNCE = 0x9; +constexpr uint8_t MESSAGE_TYPE_UNSUBSCRIBE = 0xA; +constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_DONE = 0xB; +constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_CANCEL = 0xC; +constexpr uint8_t MESSAGE_TYPE_TRACK_STATUS_REQUEST = 0xD; +constexpr uint8_t MESSAGE_TYPE_TRACK_STATUS = 0xE; +constexpr uint8_t MESSAGE_TYPE_GOAWAY = 0x10; +constexpr uint8_t MESSAGE_TYPE_CLIENT_SETUP = 0x40; +constexpr uint8_t MESSAGE_TYPE_SERVER_SETUP = 0x41; +constexpr uint8_t MESSAGE_TYPE_STREAM_HEADER_TRACK = 0x50; +constexpr uint8_t MESSAGE_TYPE_STREAM_HEADER_GROUP = 0x51; + +// TODO (Suhas): rename it to StreamMapping +enum ForwardingPreference : uint8_t { + StreamPerGroup = 0, + StreamPerObject, + StreamPerPriority, + StreamPerTrack, + Datagram +}; + + +// +// Parameters +// + +enum struct ParameterType : uint8_t { + Role = 0x0, + Path = 0x1, + AuthorizationInfo = 0x2, // version specific, unused + Invalid = 0xFF, // used internally. +}; + +struct MoqParameter{ + std::optional param_type; + uintVar_t param_length; + bytes param_value; +}; + +// +// Setup +// + +struct MoqClientSetup { + std::vector supported_versions; + MoqParameter role_parameter; + MoqParameter path_parameter; +}; + +struct MoqServerSetup { + Version supported_version; + MoqParameter role_parameter; + MoqParameter path_parameter; +}; + +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqClientSetup &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqClientSetup &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqServerSetup &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqServerSetup &msg); + +// +// Subscribe +// +enum struct LocationMode: uint8_t { + None = 0x0, + Absolute, + RelativePrevious, + RelativeNext +}; + +struct Location { + LocationMode mode; + std::optional value; +}; + +struct MoqSubscribe { + SubscribeId subscribe_id; + TrackAlias track_alias; + TrackNamespace track_namespace; + TrackName track_name; + Location start_group; + Location start_object; + Location end_group; + Location end_object; + std::vector track_params; +}; + +struct MoqSubscribeOk { + SubscribeId subscribe_id; + uintVar_t expires; + bool content_exists; + std::optional largest_group; + std::optional largest_object; +}; + + +struct MoqSubscribeError { + SubscribeId subscribe_id; + ErrorCode err_code; + ReasonPhrase reason_phrase; + TrackAlias track_alias; +}; + +struct MoqUnsubscribe { + SubscribeId subscribe_id; +}; + +struct MoqSubscribeDone +{ + SubscribeId subscribe_id; + StatusCode status_code; + ReasonPhrase reason_phrase; + bool content_exists; + GroupId final_group_id; + ObjectId final_object_id; +}; + +MessageBuffer& operator<<(MessageBuffer &buffer, const Location &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, Location &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribe &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribe &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeOk &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeOk &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeError &msg); +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeDone &msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeDone &msg); + + + +// +// Announce +// + +struct MoqAnnounce { + TrackNamespace track_namespace; + std::vector params; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounce &msg); +private: + uint64_t num_params {0}; + MoqParameter current_param{}; +}; + +struct MoqAnnounceOk { + TrackNamespace track_namespace; +}; + +struct MoqAnnounceError { + TrackNamespace track_namespace; + ErrorCode err_code; + ReasonPhrase reason_phrase; +}; + +struct MoqUnannounce { + TrackNamespace track_namespace; +}; + +struct MoqAnnounceCancel { + TrackNamespace track_namespace; +}; + + +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounce& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounce &msg); +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounce& msg); +bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounce &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceOk& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounceOk &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceError& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounceError &msg); +MessageBuffer &operator<<(MessageBuffer &buffer, const MoqUnannounce &msg); +MessageBuffer &operator>>(MessageBuffer &buffer, MoqUnannounce &msg); +MessageBuffer &operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg); +MessageBuffer &operator>>(MessageBuffer &buffer, MoqAnnounceCancel &msg); + + +// +// GoAway +// +struct MoqGoaway { + quicr::bytes new_session_uri; +}; + +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqGoaway& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqGoaway &msg); + + +// +// Object +// +struct MoqObjectStream { + SubscribeId subscribe_id; + TrackAlias track_alias; + GroupId group_id; + ObjectId object_id; + ObjectPriority priority; + quicr::bytes payload; +}; + +struct MoqObjectDatagram { + SubscribeId subscribe_id; + TrackAlias track_alias; + GroupId group_id; + ObjectId object_id; + ObjectPriority priority; + quicr::bytes payload; +}; + + +struct MoqStreamHeaderTrack { + SubscribeId subscribe_id; + TrackAlias track_alias; + ObjectPriority priority; +}; + +struct MoqStreamTrackObject { + GroupId group_id; + ObjectId object_id; + quicr::bytes payload; +}; + + +struct MoqStreamHeaderGroup { + SubscribeId subscribe_id; + TrackAlias track_alias; + GroupId group_id; + ObjectPriority priority; +}; + +struct MoqStreamGroupObject { + ObjectId object_id; + quicr::bytes payload; +}; + +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqObjectStream& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqObjectStream &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqObjectDatagram& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqObjectDatagram &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamHeaderTrack& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamHeaderTrack &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamTrackObject& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamTrackObject &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamHeaderGroup& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamHeaderGroup &msg); +MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamGroupObject& msg); +MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg); + +// utility +std::tuple to_locations(const SubscribeIntent& intent); +MessageBuffer& operator<<(MessageBuffer& buffer, const std::vector& val); +MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); +MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); + +template +MessageBuffer& operator<<(MessageBuffer& buffer, const std::optional& val); +template +MessageBuffer& operator>>(MessageBuffer& msg, std::optional& val); +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f32b72c..7a383afc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(quicr) target_sources(quicr PRIVATE message_buffer.cpp encode.cpp + moq_messages.cpp quicr_client.cpp quicr_client_raw_session.cpp quicr_server.cpp diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp new file mode 100644 index 00000000..a698abe2 --- /dev/null +++ b/src/moq_messages.cpp @@ -0,0 +1,643 @@ +#include "quicr/moq_messages.h" +#include "quicr/message_buffer.h" +#include "quicr/encode.h" + +namespace quicr::messages { + +// +// Optional +// + +template +MessageBuffer& operator<<(MessageBuffer& buffer, const std::optional& val) { + if (val.has_value()) { + buffer << val.value(); + } + return buffer; +} + +template +MessageBuffer& operator>>(MessageBuffer& buffer, std::optional& val) { + T val_in{}; + buffer >> val_in; + val = val_in; + return buffer; +} + + +// +// MoqParameter +// + +MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter ¶m) { + buffer << param.param_type; + buffer << param.param_length; + if (param.param_length) { + buffer << param.param_value; + } + + return buffer; +} + +MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter ¶m) { + buffer >> param.param_type; + buffer >> param.param_length; + if (static_cast(param.param_length) > 0) { + buffer >> param.param_value; + } + return buffer; +} + + +// +// Subscribe +// + + +MessageBuffer& operator<<(MessageBuffer &buffer, const Location &msg) { + if (msg.mode == LocationMode::None) { + buffer << static_cast(msg.mode); + return buffer; + } + buffer << static_cast(msg.mode); + buffer << msg.value.value(); + return buffer; +} + +MessageBuffer& operator>>(MessageBuffer &buffer, Location &msg) { + uint8_t mode {0}; + buffer >> mode; + msg.mode = static_cast(mode); + if (static_cast(mode) != LocationMode::None) { + uintVar_t loc_val{0}; + buffer >> loc_val; + msg.value = loc_val; + } + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqSubscribe &msg) { + buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE); + buffer << msg.subscribe_id; + buffer << msg.track_alias; + buffer << msg.track_namespace; + buffer << msg.track_name; + buffer << msg.start_group; + buffer << msg.start_object; + buffer << msg.end_group; + buffer << msg.end_object; + buffer << static_cast(msg.track_params.size()); + for (const auto& param: msg.track_params) { + buffer << param.param_type; + buffer << param.param_length; + buffer << param.param_value; + } + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqSubscribe &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.track_alias; + buffer >> msg.track_namespace; + buffer >> msg.track_name; + buffer >> msg.start_group; + buffer >> msg.start_object; + buffer >> msg.end_group; + buffer >> msg.end_object; + uintVar_t num_params {0}; + buffer >> num_params; + auto track_params = std::vector{}; + while(static_cast(num_params) > 0) { + auto param = MoqParameter{}; + buffer >> param.param_type; + buffer >> param.param_length; + buffer >> param.param_value; + track_params.push_back(std::move(param)); + num_params = num_params - 1; + } + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg) { + buffer << static_cast(MESSAGE_TYPE_UNSUBSCRIBE); + buffer << msg.subscribe_id; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg) { + buffer >> msg.subscribe_id; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqSubscribeOk &msg) { + buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_OK); + buffer << msg.subscribe_id; + buffer << msg.expires; + buffer << static_cast(msg.content_exists); + if (msg.content_exists) { + buffer << msg.largest_group; + buffer << msg.largest_object; + return buffer; + } + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqSubscribeOk &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.expires; + uint8_t content_exists {0}; + buffer >> content_exists; + if(content_exists > 1) { + throw std::runtime_error("Invalid Context Exists Value"); + } + + if (content_exists == 1) { + msg.content_exists = true; + buffer >> msg.largest_group; + buffer >> msg.largest_object; + return buffer; + } + + msg.content_exists = false; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg) { + buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR); + buffer << msg.subscribe_id; + buffer << msg.err_code; + buffer << msg.reason_phrase; + buffer << msg.track_alias; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqSubscribeError &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.err_code; + buffer >> msg.reason_phrase; + buffer >> msg.track_alias; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqSubscribeDone &msg) { + buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE); + buffer << msg.subscribe_id; + buffer << msg.status_code; + buffer << msg.reason_phrase; + buffer << uint8_t(msg.content_exists); + if (msg.content_exists) { + buffer << msg.final_group_id; + buffer << msg.final_object_id; + } + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqSubscribeDone&msg) { + buffer >> msg.subscribe_id; + buffer >> msg.status_code; + buffer >> msg.reason_phrase; + uint8_t context_exists {0}; + buffer >> context_exists; + if (context_exists > 1) { + throw std::runtime_error("Incorrect Context Exists value"); + } + + if (context_exists == 1) { + msg.content_exists = true; + buffer >> msg.final_group_id; + buffer >> msg.final_object_id; + return buffer; + } + + msg.content_exists = false; + return buffer; +} + + +// +// Announce +// + + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounce& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE))); + buffer.push_lv(msg.track_namespace); + buffer.push(qtransport::to_uintV(static_cast(0))); + return buffer; +} + +bool operator>>(qtransport::StreamBuffer &buffer, + MoqAnnounce &msg) { + + // read namespace + if (msg.track_namespace.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = *val; + } + + if (!msg.num_params) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.num_params = *val; + } + + // parse each param + while (msg.num_params > 0) { + if (!msg.current_param.param_type) { + auto val = buffer.front(); + if (!val) { + return false; + } + msg.current_param.param_type = *val; + buffer.pop(); + } + + // decode param_len: + auto param = buffer.decode_bytes(); + if (!param) { + return false; + } + + msg.current_param.param_length = param->size(); + msg.current_param.param_value = param.value(); + msg.params.push_back(msg.current_param); + msg.current_param = {}; + msg.num_params -= 1; + } + + return true; +} + +MessageBuffer&operator<<(MessageBuffer &buffer, const MoqAnnounce &msg) { + buffer << static_cast(MESSAGE_TYPE_ANNOUNCE); + buffer << msg.track_namespace; + // TODO(Suhas): Fix this once we have params + buffer << uintVar_t{0}; + return buffer; +} + + +MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounce &msg) { + buffer >> msg.track_namespace; + uintVar_t num_params {0}; + buffer >> num_params; + auto track_params = std::vector{}; + while(static_cast(num_params) > 0) { + auto param = MoqParameter{}; + buffer >> param.param_type; + buffer >> param.param_length; + buffer >> param.param_value; + track_params.push_back(std::move(param)); + num_params = num_params - 1; + } + return buffer; +} + + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqAnnounceOk &msg) { + buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_OK); + buffer << msg.track_namespace; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqAnnounceOk &msg) { + buffer >> msg.track_namespace; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqAnnounceError &msg) { + buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR); + buffer << msg.track_namespace; + buffer << msg.err_code; + buffer << msg.reason_phrase; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqAnnounceError &msg) { + buffer >> msg.track_namespace; + buffer >> msg.err_code; + buffer >> msg.reason_phrase; + return buffer; +} + + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqUnannounce &msg) { + buffer << static_cast(MESSAGE_TYPE_UNANNOUNCE); + buffer << msg.track_namespace; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqUnannounce &msg) { + buffer >> msg.track_namespace; + return buffer; +} + + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg) { + buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL); + buffer << msg.track_namespace; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqAnnounceCancel &msg) { + buffer >> msg.track_namespace; + return buffer; +} + + +// +// Goaway +// + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqGoaway &msg) { + buffer << static_cast(MESSAGE_TYPE_GOAWAY); + buffer << msg.new_session_uri; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqGoaway &msg) { + buffer >> msg.new_session_uri; + return buffer; +} + +// +// Object +// + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqObjectStream &msg) { + buffer << static_cast(MESSAGE_TYPE_OBJECT_STREAM); + buffer << msg.subscribe_id; + buffer << msg.track_alias; + buffer << msg.group_id; + buffer << msg.object_id; + buffer << msg.priority; + buffer << msg.payload; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqObjectStream &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.track_alias; + buffer >> msg.group_id; + buffer >> msg.object_id; + buffer >> msg.priority; + buffer >> msg.payload; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqObjectDatagram &msg) { + buffer << MESSAGE_TYPE_OBJECT_DATAGRAM; + buffer << msg.subscribe_id; + buffer << msg.track_alias; + buffer << msg.group_id; + buffer << msg.object_id; + buffer << msg.priority; + buffer << msg.payload; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqObjectDatagram &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.track_alias; + buffer >> msg.group_id; + buffer >> msg.object_id; + buffer >> msg.priority; + buffer >> msg.payload; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqStreamTrackObject &msg) { + buffer << msg.group_id; + buffer << msg.object_id; + buffer << msg.payload; + return buffer; +} + +MessageBuffer& +operator>>(MessageBuffer &buffer, MoqStreamTrackObject &msg) { + buffer >> msg.group_id; + buffer >> msg.object_id; + buffer >> msg.payload; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqStreamHeaderTrack &msg) { + buffer << MESSAGE_TYPE_STREAM_HEADER_TRACK; + buffer << msg.subscribe_id; + buffer << msg.track_alias; + buffer << msg.priority; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqStreamHeaderTrack &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.track_alias; + buffer >> msg.priority; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqStreamHeaderGroup &msg) { + buffer << MESSAGE_TYPE_STREAM_HEADER_GROUP; + buffer << msg.subscribe_id; + buffer << msg.track_alias; + buffer << msg.group_id; + buffer << msg.priority; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqStreamHeaderGroup &msg) { + buffer >> msg.subscribe_id; + buffer >> msg.track_alias; + buffer >> msg.group_id; + buffer >> msg.priority; + return buffer; +} + +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqStreamGroupObject &msg) { + buffer << msg.object_id; + buffer << msg.payload; + return buffer; +} + +MessageBuffer& +operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg) { + buffer >> msg.object_id; + buffer >> msg.payload; + return buffer; +} + +std::tuple to_locations(const SubscribeIntent& intent) { + auto none_location = Location {.mode = LocationMode::None, .value = std::nullopt}; + + /* + * Sequence: 0 1 2 3 4 [5] [6] ... + ^ + Largest Sequence + RelativePrevious Value: 4 3 2 1 0 + RelativeNext Value: 0 1 ... + */ + switch (intent) { + case SubscribeIntent::immediate: + return std::make_tuple(Location{.mode=LocationMode::RelativePrevious, .value=0}, //StartGroup + none_location, // EndGroup + Location{.mode=LocationMode::RelativePrevious, .value=0}, //StartObject + none_location); // EndObject + case SubscribeIntent::sync_up: + throw std::runtime_error("Intent Unsupported for Subscribe"); + case SubscribeIntent::wait_up: + throw std::runtime_error("Intent Unsupported for Subscribe"); + default: + throw std::runtime_error("Bad Intent for Subscribe"); + } + +} + + +// Setup + +// vector encode/decode +MessageBuffer& +operator<<(MessageBuffer& buffer, const std::vector& val) +{ + buffer << static_cast(val.size()); + for(uint64_t i = 0; i < val.size(); i++) { + buffer << val[i]; + } + + return buffer; +} + +MessageBuffer& +operator>>(MessageBuffer& msg, std::vector& val) +{ + auto vec_size = uintVar_t(0); + msg >> vec_size; + auto version = std::vector(); + version.resize((uint64_t) vec_size); + val.resize((uint64_t) vec_size); + + // TODO (Suhas): This needs revisiting + for(uint64_t i = 0; i < version.size(); i++) { + msg >> version[i]; + } + val = std::move(version); + return msg; +} + + +// Client Setup message +MessageBuffer & +operator<<(MessageBuffer &buffer, const MoqClientSetup &msg) { + buffer << static_cast(MESSAGE_TYPE_CLIENT_SETUP); + // versions + buffer << static_cast(msg.supported_versions.size()); + for (const auto& ver: msg.supported_versions) { + buffer << static_cast(ver); + } + + // TODO (Suhas): Add support for PATH Param + // num params + buffer << uintVar_t{1}; + // role param + buffer << static_cast(msg.role_parameter.param_type.value()); + buffer << uintVar_t(1); + buffer << msg.role_parameter.param_value; + + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqClientSetup &msg) { + uintVar_t num_versions {0}; + buffer >> num_versions; + msg.supported_versions.resize(num_versions); + for(size_t i = 0; i < num_versions; i++) { + uintVar_t version{0}; + buffer >> version; + msg.supported_versions.push_back(version); + } + + uintVar_t num_params {0}; + buffer >> num_params; + if (static_cast (num_params) == 0) { + return buffer; + } + + while (static_cast(num_params) > 0) { + uint8_t param_type {0}; + buffer >> param_type; + auto param_type_enum = static_cast (param_type); + switch(param_type_enum) { + case ParameterType::Role: { + msg.role_parameter.param_type = param_type; + buffer >> msg.role_parameter.param_length; + buffer >> msg.role_parameter.param_value; + } + break; + case ParameterType::Path: { + msg.path_parameter.param_type = param_type; + buffer >> msg.path_parameter.param_length; + buffer >> msg.path_parameter.param_value; + } + break; + default: + throw std::runtime_error("Unsupported Parameter Type for ClientSetup"); + } + num_params = num_params - 1; + } + + + return buffer; +} + +// Server Setup message +MessageBuffer& +operator<<(MessageBuffer &buffer, const MoqServerSetup &msg) { + buffer << static_cast(MESSAGE_TYPE_SERVER_SETUP); + buffer << msg.supported_version; + return buffer; +} + +MessageBuffer & +operator>>(MessageBuffer &buffer, MoqServerSetup &msg) { + buffer >> msg.supported_version; + return buffer; +} +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d0320ec9..57469693 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,7 +5,9 @@ add_executable(quicr_test quicr_client.cpp stream_buffer.cpp quicr_server.cpp - encode.cpp) + moq_messages.cpp + encode.cpp + moq_messages.cpp) target_include_directories(quicr_test PRIVATE ${PROJECT_SOURCE_DIR}/src) target_link_libraries(quicr_test PRIVATE quicr doctest::doctest) diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp new file mode 100644 index 00000000..3be9e0d2 --- /dev/null +++ b/test/moq_messages.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include + +using namespace quicr; +using namespace quicr::messages; + +static bytes from_ascii(const std::string& ascii) +{ + return std::vector(ascii.begin(), ascii.end()); +} + +const quicr::bytes TRACK_NAMESPACE_CONF = from_ascii("moqt://conf.example.com/conf/1"); +const quicr::bytes TRACK_NAME_ALICE_VIDEO = from_ascii("alice/video"); +const uintVar_t TRACK_ALIAS_ALICE_VIDEO = 0xA11CE; + +TEST_CASE("Announce Message encode/decode") +{ + qtransport::StreamBuffer buffer; + std::vector net_data; + + auto announce = MoqAnnounce {}; + announce.track_namespace = TRACK_NAMESPACE_CONF; + announce.params = {}; + + buffer << announce; + + auto message_type = buffer.decode_uintV(); + CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE)); + + MoqAnnounce announce_out; + buffer >> announce_out; + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); + CHECK_EQ(0, announce_out.params.size()); +} \ No newline at end of file From 791acef9450050bb67b694c81169b71791d9e7e9 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 10:59:13 -0700 Subject: [PATCH 02/71] add announce_ok --- include/quicr/moq_messages.h | 9 ++++--- src/moq_messages.cpp | 47 ++++++++++++------------------------ test/moq_messages.cpp | 17 +++++++++++++ 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 1c33bebd..7e296166 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -173,6 +173,8 @@ struct MoqAnnounce { TrackNamespace track_namespace; std::vector params; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounce &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounce& msg); private: uint64_t num_params {0}; MoqParameter current_param{}; @@ -180,6 +182,9 @@ struct MoqAnnounce { struct MoqAnnounceOk { TrackNamespace track_namespace; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceOk &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceOk& msg); }; struct MoqAnnounceError { @@ -197,10 +202,6 @@ struct MoqAnnounceCancel { }; -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounce& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounce &msg); -qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounce& msg); -bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounce &msg); MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceOk& msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounceOk &msg); MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceError& msg); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index a698abe2..bcd17fbb 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -285,44 +285,29 @@ bool operator>>(qtransport::StreamBuffer &buffer, return true; } -MessageBuffer&operator<<(MessageBuffer &buffer, const MoqAnnounce &msg) { - buffer << static_cast(MESSAGE_TYPE_ANNOUNCE); - buffer << msg.track_namespace; - // TODO(Suhas): Fix this once we have params - buffer << uintVar_t{0}; - return buffer; -} - -MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounce &msg) { - buffer >> msg.track_namespace; - uintVar_t num_params {0}; - buffer >> num_params; - auto track_params = std::vector{}; - while(static_cast(num_params) > 0) { - auto param = MoqParameter{}; - buffer >> param.param_type; - buffer >> param.param_length; - buffer >> param.param_value; - track_params.push_back(std::move(param)); - num_params = num_params - 1; - } +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceOk& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_OK))); + buffer.push_lv(msg.track_namespace); return buffer; } +bool operator>>(qtransport::StreamBuffer &buffer, + MoqAnnounceOk &msg) { -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqAnnounceOk &msg) { - buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_OK); - buffer << msg.track_namespace; - return buffer; + // read namespace + if (msg.track_namespace.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = *val; + } + return true; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqAnnounceOk &msg) { - buffer >> msg.track_namespace; - return buffer; -} MessageBuffer & operator<<(MessageBuffer &buffer, const MoqAnnounceError &msg) { diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 3be9e0d2..3052f347 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -17,6 +17,23 @@ const quicr::bytes TRACK_NAMESPACE_CONF = from_ascii("moqt://conf.example.com/co const quicr::bytes TRACK_NAME_ALICE_VIDEO = from_ascii("alice/video"); const uintVar_t TRACK_ALIAS_ALICE_VIDEO = 0xA11CE; +TEST_CASE("AnnounceOk Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto announce_ok = MoqAnnounceOk {}; + announce_ok.track_namespace = TRACK_NAMESPACE_CONF; + + buffer << announce_ok; + + auto message_type = buffer.decode_uintV(); + CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE_OK)); + + MoqAnnounceOk announce_ok_out; + buffer >> announce_ok_out; + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); +} + TEST_CASE("Announce Message encode/decode") { qtransport::StreamBuffer buffer; From a80a07d1be2af443ea98a9660745211c5f0fc545 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 12:05:22 -0700 Subject: [PATCH 03/71] make decode test a streaming test --- include/quicr/moq_messages.h | 15 +++++------ src/moq_messages.cpp | 50 ++++++++++++++++++++++++++---------- test/moq_messages.cpp | 31 +++++++++++++++++----- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 7e296166..a0cbf8c6 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -12,7 +12,7 @@ namespace quicr::messages { using Version = uintVar_t; using TrackNamespace = quicr::bytes; using TrackName = quicr::bytes; -using ErrorCode = uintVar_t; +using ErrorCode = uint64_t; using StatusCode = uintVar_t; using ReasonPhrase = quicr::bytes; using GroupId = uintVar_t; @@ -188,9 +188,12 @@ struct MoqAnnounceOk { }; struct MoqAnnounceError { - TrackNamespace track_namespace; - ErrorCode err_code; - ReasonPhrase reason_phrase; + std::optional track_namespace; + std::optional err_code; + std::optional reason_phrase; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceError &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceError* msg); }; struct MoqUnannounce { @@ -202,10 +205,6 @@ struct MoqAnnounceCancel { }; -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceOk& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounceOk &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqAnnounceError& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqAnnounceError &msg); MessageBuffer &operator<<(MessageBuffer &buffer, const MoqUnannounce &msg); MessageBuffer &operator>>(MessageBuffer &buffer, MoqUnannounce &msg); MessageBuffer &operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index bcd17fbb..4ae0ba96 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -293,8 +293,7 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& return buffer; } -bool operator>>(qtransport::StreamBuffer &buffer, - MoqAnnounceOk &msg) { +bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceOk &msg) { // read namespace if (msg.track_namespace.empty()) @@ -309,21 +308,44 @@ bool operator>>(qtransport::StreamBuffer &buffer, } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqAnnounceError &msg) { - buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR); - buffer << msg.track_namespace; - buffer << msg.err_code; - buffer << msg.reason_phrase; +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceError& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR))); + buffer.push_lv(msg.track_namespace.value()); + buffer.push(qtransport::to_uintV(msg.err_code.value())); + buffer.push_lv(msg.reason_phrase.value()); return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqAnnounceError &msg) { - buffer >> msg.track_namespace; - buffer >> msg.err_code; - buffer >> msg.reason_phrase; - return buffer; +bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceError &msg) { + + // read namespace + if (!msg.track_namespace) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = *val; + } + + if (!msg.err_code) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + + msg.err_code = *val; + } + while (!msg.reason_phrase > 0) { + auto reason = buffer.decode_bytes(); + if (!reason) { + return false; + } + msg.reason_phrase = reason; + } + + return true; } diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 3052f347..2c13fa6d 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -24,7 +24,7 @@ TEST_CASE("AnnounceOk Message encode/decode") auto announce_ok = MoqAnnounceOk {}; announce_ok.track_namespace = TRACK_NAMESPACE_CONF; - buffer << announce_ok; + buffer << announce_ok; auto message_type = buffer.decode_uintV(); CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE_OK)); @@ -37,19 +37,36 @@ TEST_CASE("AnnounceOk Message encode/decode") TEST_CASE("Announce Message encode/decode") { qtransport::StreamBuffer buffer; - std::vector net_data; auto announce = MoqAnnounce {}; announce.track_namespace = TRACK_NAMESPACE_CONF; announce.params = {}; - buffer << announce; + std::vector net_data = buffer.front(buffer.size()); - auto message_type = buffer.decode_uintV(); - CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE)); + // ingress buffer + qtransport::StreamBuffer in_buffer; + std::optional message_type; MoqAnnounce announce_out; - buffer >> announce_out; + for (auto& v: net_data) { + in_buffer.push(v); + if (!message_type) { + message_type = in_buffer.decode_uintV(); + if(!message_type) { + continue; + } + CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE)); + continue; + } + + bool got = in_buffer >> announce_out; + if (!got) { + continue; + } + } + + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); CHECK_EQ(0, announce_out.params.size()); -} \ No newline at end of file +} From e0b13d9e2851b0f9b77176087aa0f9fac8a56489 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 12:11:49 -0700 Subject: [PATCH 04/71] make announce_ok a streaming test --- test/moq_messages.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 2c13fa6d..1295ae62 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -26,11 +26,29 @@ TEST_CASE("AnnounceOk Message encode/decode") buffer << announce_ok; - auto message_type = buffer.decode_uintV(); - CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE_OK)); + std::vector net_data = buffer.front(buffer.size()); + + // ingress buffer + qtransport::StreamBuffer in_buffer; + std::optional message_type; MoqAnnounceOk announce_ok_out; - buffer >> announce_ok_out; + for (auto& v: net_data) { + in_buffer.push(v); + if (!message_type) { + message_type = in_buffer.decode_uintV(); + if (!message_type) { + continue; + } + CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE_OK)); + continue; + } + bool got = in_buffer >> announce_ok_out; + if (!got) { + continue; + } + } + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); } @@ -66,7 +84,6 @@ TEST_CASE("Announce Message encode/decode") } } - CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); CHECK_EQ(0, announce_out.params.size()); } From 8524f2b76df809e684932cb2caf8f971d475cd0b Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Fri, 31 May 2024 16:59:36 -0700 Subject: [PATCH 05/71] Stub out new MOQ delegates and instance --- cmd/really/reallyTest.cpp | 6 ++++- dependencies/transport | 2 +- include/quicr/moq_delegate_instance.h | 24 ++++++++++++++++++++ include/quicr/moq_delegate_track_publish.h | 16 +++++++++++++ include/quicr/moq_delegate_track_subscribe.h | 18 +++++++++++++++ include/quicr/moq_instance.h | 18 +++++++++++++++ src/CMakeLists.txt | 2 ++ src/moq_instance.cpp | 11 +++++++++ src/moq_session.cpp | 11 +++++++++ src/moq_session.h | 18 +++++++++++++++ 10 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 include/quicr/moq_delegate_instance.h create mode 100644 include/quicr/moq_delegate_track_publish.h create mode 100644 include/quicr/moq_delegate_track_subscribe.h create mode 100644 include/quicr/moq_instance.h create mode 100644 src/moq_instance.cpp create mode 100644 src/moq_session.cpp create mode 100644 src/moq_session.h diff --git a/cmd/really/reallyTest.cpp b/cmd/really/reallyTest.cpp index c199fa28..2996cf8e 100644 --- a/cmd/really/reallyTest.cpp +++ b/cmd/really/reallyTest.cpp @@ -113,7 +113,11 @@ int do_publisher(cantina::LoggerPointer logger, return -1; } - logger->info << "Received intent response. Type message and press ENTER to publish. Type exit to end program." << std::flush; + logger->info << "Received intent response." << std::flush; + + std::cout << "-----------------------------------------------------------------------" << std::endl; + std::cout << " Type a message and press ENTER to publish. Type the word exit to end program." << std::flush; + std::cout << "-----------------------------------------------------------------------" << std::endl; while (true) { std::string msg; diff --git a/dependencies/transport b/dependencies/transport index 1d13a363..ebf29645 160000 --- a/dependencies/transport +++ b/dependencies/transport @@ -1 +1 @@ -Subproject commit 1d13a3631b20e3d1355f484d78132fc19ca174e4 +Subproject commit ebf29645ce4eadb057a09d6b641c765f487e400d diff --git a/include/quicr/moq_delegate_instance.h b/include/quicr/moq_delegate_instance.h new file mode 100644 index 00000000..183bc24a --- /dev/null +++ b/include/quicr/moq_delegate_instance.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#pragma once + +namespace quicr { + +/** + * @brief Delegate for all MOQ/MOQT callbacks and interaction + * + * @details A new MOQ instance is created with this delegate as a shared pointer. + * All interaction with the instance is handled via this delegate. This delegate + * is a base class implementation and interface for callbacks. + */ + +class MOQInstanceDelegate +{ + +}; + +} // namespace quicr diff --git a/include/quicr/moq_delegate_track_publish.h b/include/quicr/moq_delegate_track_publish.h new file mode 100644 index 00000000..b9e0759d --- /dev/null +++ b/include/quicr/moq_delegate_track_publish.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#pragma once + +namespace quicr { + +class MOQTrackPublishDelegate +{ + +}; + +} // namespace quicr diff --git a/include/quicr/moq_delegate_track_subscribe.h b/include/quicr/moq_delegate_track_subscribe.h new file mode 100644 index 00000000..945df79c --- /dev/null +++ b/include/quicr/moq_delegate_track_subscribe.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#pragma once + + +namespace quicr { + +class MOQTrackSubscribeDelegate +{ + +}; + + +} // namespace quicr diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h new file mode 100644 index 00000000..94f281fb --- /dev/null +++ b/include/quicr/moq_instance.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#pragma once + +#include +#include +#include + +namespace quicr { + +class MOQInstance { +}; + +} // namespace quicr diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a383afc..71e0adb3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,8 @@ target_sources(quicr PRIVATE quicr_client_raw_session.cpp quicr_server.cpp quicr_server_raw_session.cpp + moq_instance.cpp + moq_session.cpp ) if(NOT LIBQUICR_WITHOUT_INFLUXDB) diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp new file mode 100644 index 00000000..e36196c4 --- /dev/null +++ b/src/moq_instance.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#include + +namespace quicr { + +} // namespace quicr diff --git a/src/moq_session.cpp b/src/moq_session.cpp new file mode 100644 index 00000000..7f0d5102 --- /dev/null +++ b/src/moq_session.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#include "moq_session.h" + +namespace quicr { + +} // namespace quicr diff --git a/src/moq_session.h b/src/moq_session.h new file mode 100644 index 00000000..45efad6a --- /dev/null +++ b/src/moq_session.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ + +#pragma once + +#include "moq_session.h" + +namespace quicr { + +class MOQSession +{ + +}; + +} // namespace quicr From 0398433a581636551a364e5a2c66006129a4a5eb Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 18:21:36 -0700 Subject: [PATCH 06/71] refactor test --- include/quicr/moq_messages.h | 5 ++- src/moq_messages.cpp | 27 ++++++++------ test/moq_messages.cpp | 71 ++++++++++++++---------------------- 3 files changed, 46 insertions(+), 57 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index a0cbf8c6..d39e62d0 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -198,6 +198,9 @@ struct MoqAnnounceError { struct MoqUnannounce { TrackNamespace track_namespace; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqUnannounce &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqUnannounce* msg); }; struct MoqAnnounceCancel { @@ -205,8 +208,6 @@ struct MoqAnnounceCancel { }; -MessageBuffer &operator<<(MessageBuffer &buffer, const MoqUnannounce &msg); -MessageBuffer &operator>>(MessageBuffer &buffer, MoqUnannounce &msg); MessageBuffer &operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg); MessageBuffer &operator>>(MessageBuffer &buffer, MoqAnnounceCancel &msg); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 4ae0ba96..f0b2a597 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -285,7 +285,6 @@ bool operator>>(qtransport::StreamBuffer &buffer, return true; } - qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounceOk& msg){ buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_OK))); @@ -348,20 +347,26 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceError &msg return true; } - -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqUnannounce &msg) { - buffer << static_cast(MESSAGE_TYPE_UNANNOUNCE); - buffer << msg.track_namespace; +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqUnannounce& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_UNANNOUNCE))); + buffer.push_lv(msg.track_namespace); return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqUnannounce &msg) { - buffer >> msg.track_namespace; - return buffer; -} +bool operator>>(qtransport::StreamBuffer &buffer, MoqUnannounce &msg) { + // read namespace + if (msg.track_namespace.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = *val; + } + return true; +} MessageBuffer & operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg) { diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 1295ae62..8c10cda3 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -17,38 +17,42 @@ const quicr::bytes TRACK_NAMESPACE_CONF = from_ascii("moqt://conf.example.com/co const quicr::bytes TRACK_NAME_ALICE_VIDEO = from_ascii("alice/video"); const uintVar_t TRACK_ALIAS_ALICE_VIDEO = 0xA11CE; -TEST_CASE("AnnounceOk Message encode/decode") -{ - qtransport::StreamBuffer buffer; - - auto announce_ok = MoqAnnounceOk {}; - announce_ok.track_namespace = TRACK_NAMESPACE_CONF; - - buffer << announce_ok; - - std::vector net_data = buffer.front(buffer.size()); - - // ingress buffer +template +bool verify(std::vector& net_data, uint64_t message_type, T& message) { qtransport::StreamBuffer in_buffer; - std::optional message_type; - - MoqAnnounceOk announce_ok_out; + std::optional msg_type; + bool done = false; for (auto& v: net_data) { in_buffer.push(v); - if (!message_type) { - message_type = in_buffer.decode_uintV(); - if (!message_type) { + if (!msg_type) { + msg_type = in_buffer.decode_uintV(); + if (!msg_type) { continue; } - CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE_OK)); + CHECK_EQ(*msg_type, message_type); continue; } - bool got = in_buffer >> announce_ok_out; - if (!got) { - continue; + + done = in_buffer >> message; + if (done) { + break; } } + return done; +} + +TEST_CASE("AnnounceOk Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto announce_ok = MoqAnnounceOk {}; + announce_ok.track_namespace = TRACK_NAMESPACE_CONF; + buffer << announce_ok; + + std::vector net_data = buffer.front(buffer.size()); + MoqAnnounceOk announce_ok_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_OK), announce_ok_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); } @@ -61,29 +65,8 @@ TEST_CASE("Announce Message encode/decode") announce.params = {}; buffer << announce; std::vector net_data = buffer.front(buffer.size()); - - // ingress buffer - qtransport::StreamBuffer in_buffer; - std::optional message_type; - MoqAnnounce announce_out; - for (auto& v: net_data) { - in_buffer.push(v); - if (!message_type) { - message_type = in_buffer.decode_uintV(); - if(!message_type) { - continue; - } - CHECK_EQ(*message_type, static_cast(MESSAGE_TYPE_ANNOUNCE)); - continue; - } - - bool got = in_buffer >> announce_out; - if (!got) { - continue; - } - } - + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE), announce_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); CHECK_EQ(0, announce_out.params.size()); } From 175f9888b8896646a24d30680939ba0919c01ead Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 18:30:35 -0700 Subject: [PATCH 07/71] add announce_err, unannounce --- include/quicr/moq_messages.h | 5 +++-- test/moq_messages.cpp | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index d39e62d0..5ccd5213 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -191,16 +191,17 @@ struct MoqAnnounceError { std::optional track_namespace; std::optional err_code; std::optional reason_phrase; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceError &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, - const MoqAnnounceError* msg); + const MoqAnnounceError& msg); }; struct MoqUnannounce { TrackNamespace track_namespace; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqUnannounce &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, - const MoqUnannounce* msg); + const MoqUnannounce& msg); }; struct MoqAnnounceCancel { diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 8c10cda3..9ac09aad 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -18,7 +18,8 @@ const quicr::bytes TRACK_NAME_ALICE_VIDEO = from_ascii("alice/video"); const uintVar_t TRACK_ALIAS_ALICE_VIDEO = 0xA11CE; template -bool verify(std::vector& net_data, uint64_t message_type, T& message) { +bool verify(std::vector& net_data, uint64_t message_type, T& message, size_t slice_depth=1) { + // TODO: support size_depth > 1, if needed qtransport::StreamBuffer in_buffer; std::optional msg_type; bool done = false; @@ -70,3 +71,35 @@ TEST_CASE("Announce Message encode/decode") CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); CHECK_EQ(0, announce_out.params.size()); } + +TEST_CASE("Unannounce Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto unannounce = MoqUnannounce {}; + unannounce.track_namespace = TRACK_NAMESPACE_CONF; + buffer << unannounce; + + std::vector net_data = buffer.front(buffer.size()); + MoqAnnounceOk announce_ok_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_UNANNOUNCE), announce_ok_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); +} + +TEST_CASE("AnnounceError Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto announce_err = MoqAnnounceError {}; + announce_err.track_namespace = TRACK_NAMESPACE_CONF; + announce_err.err_code = 0x1234; + announce_err.reason_phrase = quicr::bytes{0x1,0x2,0x3}; + buffer << announce_err; + + std::vector net_data = buffer.front(buffer.size()); + MoqAnnounceError announce_err_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR), announce_err_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_err_out.track_namespace); + CHECK_EQ(announce_err.err_code, announce_err_out.err_code); + CHECK_EQ(announce_err.reason_phrase, announce_err_out.reason_phrase); +} \ No newline at end of file From 2cc7171d05b23fcb85e7a4d6d1408994233ab448 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Fri, 31 May 2024 18:35:52 -0700 Subject: [PATCH 08/71] add announce_cancel --- include/quicr/moq_messages.h | 7 +++---- src/moq_messages.cpp | 25 ++++++++++++++++--------- test/moq_messages.cpp | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 5ccd5213..0ffe1028 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -206,13 +206,12 @@ struct MoqUnannounce { struct MoqAnnounceCancel { TrackNamespace track_namespace; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceCancel &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceCancel& msg); }; -MessageBuffer &operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg); -MessageBuffer &operator>>(MessageBuffer &buffer, MoqAnnounceCancel &msg); - - // // GoAway // diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index f0b2a597..54937bad 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -368,19 +368,26 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqUnannounce &msg) { return true; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqAnnounceCancel &msg) { - buffer << static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL); - buffer << msg.track_namespace; +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqAnnounceCancel& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL))); + buffer.push_lv(msg.track_namespace); return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqAnnounceCancel &msg) { - buffer >> msg.track_namespace; - return buffer; -} +bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceCancel &msg) { + // read namespace + if (msg.track_namespace.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = *val; + } + return true; +} // // Goaway diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 9ac09aad..f0094cff 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -102,4 +102,18 @@ TEST_CASE("AnnounceError Message encode/decode") CHECK_EQ(TRACK_NAMESPACE_CONF, announce_err_out.track_namespace); CHECK_EQ(announce_err.err_code, announce_err_out.err_code); CHECK_EQ(announce_err.reason_phrase, announce_err_out.reason_phrase); +} + +TEST_CASE("AnnounceCancel Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto announce_cancel = MoqAnnounceCancel {}; + announce_cancel.track_namespace = TRACK_NAMESPACE_CONF; + buffer << announce_cancel; + + std::vector net_data = buffer.front(buffer.size()); + MoqAnnounceCancel announce_cancel_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL), announce_cancel_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, announce_cancel_out.track_namespace); } \ No newline at end of file From 5648e920317c1f7cd64951e1f2aeabec3384af43 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Sat, 1 Jun 2024 04:20:49 -0700 Subject: [PATCH 09/71] add subscribe --- include/quicr/moq_messages.h | 39 +++--- src/moq_messages.cpp | 250 +++++++++++++++++++++++------------ test/moq_messages.cpp | 150 +++++++++++++++++++++ 3 files changed, 338 insertions(+), 101 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 0ffe1028..a81631a5 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -65,7 +65,7 @@ enum struct ParameterType : uint8_t { }; struct MoqParameter{ - std::optional param_type; + std::optional param_type; uintVar_t param_length; bytes param_value; }; @@ -96,28 +96,31 @@ MessageBuffer& operator>>(MessageBuffer &buffer, MoqServerSetup &msg); // // Subscribe // -enum struct LocationMode: uint8_t { +enum struct FilterType: uint64_t { None = 0x0, - Absolute, - RelativePrevious, - RelativeNext -}; - -struct Location { - LocationMode mode; - std::optional value; + LatestGroup, + LatestObject, + AbsoluteStart, + AbsoluteRange }; struct MoqSubscribe { - SubscribeId subscribe_id; - TrackAlias track_alias; + std::optional subscribe_id; + std::optional track_alias; TrackNamespace track_namespace; TrackName track_name; - Location start_group; - Location start_object; - Location end_group; - Location end_object; + FilterType filter_type {FilterType::None}; + std::optional start_group; + std::optional end_group; + std::optional start_object; + std::optional end_object; + uint64_t num_params {0}; std::vector track_params; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribe& msg); +private: + MoqParameter current_param{}; }; struct MoqSubscribeOk { @@ -150,8 +153,6 @@ struct MoqSubscribeDone ObjectId final_object_id; }; -MessageBuffer& operator<<(MessageBuffer &buffer, const Location &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, Location &msg); MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribe &msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribe &msg); MessageBuffer& operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg); @@ -283,8 +284,6 @@ MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamHeaderGroup &msg); MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamGroupObject& msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg); -// utility -std::tuple to_locations(const SubscribeIntent& intent); MessageBuffer& operator<<(MessageBuffer& buffer, const std::vector& val); MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 54937bad..952e5558 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -53,71 +53,185 @@ MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter ¶m) { // Subscribe // +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribe& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE))); + buffer.push(qtransport::to_uintV(msg.subscribe_id.value())); + buffer.push(qtransport::to_uintV(msg.track_alias.value())); + buffer.push_lv(msg.track_namespace); + buffer.push_lv(msg.track_name); + buffer.push(qtransport::to_uintV(static_cast(msg.filter_type))); -MessageBuffer& operator<<(MessageBuffer &buffer, const Location &msg) { - if (msg.mode == LocationMode::None) { - buffer << static_cast(msg.mode); - return buffer; + switch (msg.filter_type) { + case FilterType::None: + case FilterType::LatestGroup: + case FilterType::LatestObject: + break; + case FilterType::AbsoluteStart: { + buffer.push(qtransport::to_uintV(msg.start_group.value())); + buffer.push(qtransport::to_uintV(msg.start_object.value())); + } + break; + case FilterType::AbsoluteRange: + buffer.push(qtransport::to_uintV(msg.start_group.value())); + buffer.push(qtransport::to_uintV(msg.start_object.value())); + buffer.push(qtransport::to_uintV(msg.end_group.value())); + buffer.push(qtransport::to_uintV(msg.end_object.value())); + break; } - buffer << static_cast(msg.mode); - buffer << msg.value.value(); - return buffer; -} -MessageBuffer& operator>>(MessageBuffer &buffer, Location &msg) { - uint8_t mode {0}; - buffer >> mode; - msg.mode = static_cast(mode); - if (static_cast(mode) != LocationMode::None) { - uintVar_t loc_val{0}; - buffer >> loc_val; - msg.value = loc_val; + buffer.push(qtransport::to_uintV(msg.num_params)); + for (const auto& param: msg.track_params) { + buffer.push(qtransport::to_uintV(static_cast(param.param_type.value()))); + buffer.push(qtransport::to_uintV(param.param_length)); + buffer.push(param.param_value); } + return buffer; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqSubscribe &msg) { - buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE); - buffer << msg.subscribe_id; - buffer << msg.track_alias; - buffer << msg.track_namespace; - buffer << msg.track_name; - buffer << msg.start_group; - buffer << msg.start_object; - buffer << msg.end_group; - buffer << msg.end_object; - buffer << static_cast(msg.track_params.size()); - for (const auto& param: msg.track_params) { - buffer << param.param_type; - buffer << param.param_length; - buffer << param.param_value; +bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { + + if (!msg.subscribe_id.has_value()) { + auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.subscribe_id = val.value(); } - return buffer; -} -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqSubscribe &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.track_alias; - buffer >> msg.track_namespace; - buffer >> msg.track_name; - buffer >> msg.start_group; - buffer >> msg.start_object; - buffer >> msg.end_group; - buffer >> msg.end_object; - uintVar_t num_params {0}; - buffer >> num_params; - auto track_params = std::vector{}; - while(static_cast(num_params) > 0) { - auto param = MoqParameter{}; - buffer >> param.param_type; - buffer >> param.param_length; - buffer >> param.param_value; - track_params.push_back(std::move(param)); - num_params = num_params - 1; + if (!msg.track_alias.has_value()) { + auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.track_alias = val.value(); } - return buffer; + + if (msg.track_namespace.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_namespace = val.value(); + } + + if (msg.track_name.empty()) + { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.track_name = val.value(); + } + + if (msg.filter_type == FilterType::None) + { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + + auto filter = val.value(); + msg.filter_type = static_cast(filter); + } + + switch (msg.filter_type) { + case FilterType::None: + throw std::runtime_error("Malformed Filter Type"); + case FilterType::LatestGroup: + case FilterType::LatestObject: + break; + case FilterType::AbsoluteStart: + { + if (!msg.start_group.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.start_group = val.value(); + } + + if (!msg.start_object.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.start_object = val.value(); + } + } + break; + case FilterType::AbsoluteRange: + { + if (!msg.start_group.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.start_group = val.value(); + } + + if (!msg.start_object.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.start_object = val.value(); + } + + if (!msg.end_group.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.end_group = val.value(); + } + + if (!msg.end_object.has_value()) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.end_object = val.value(); + } + } + break; + } + + if (!msg.num_params) { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + msg.num_params = val.value(); + } + + // parse each param + while (msg.num_params > 0) { + if (!msg.current_param.param_type) { + auto val = buffer.front(); + if (!val) { + return false; + } + msg.current_param.param_type = *val; + buffer.pop(); + } + + // decode param_len: + auto param = buffer.decode_bytes(); + if (!param) { + return false; + } + + msg.current_param.param_length = param->size(); + msg.current_param.param_value = param.value(); + msg.track_params.push_back(msg.current_param); + msg.current_param = {}; + msg.num_params -= 1; + } + + return true; } MessageBuffer & @@ -522,32 +636,6 @@ operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg) { return buffer; } -std::tuple to_locations(const SubscribeIntent& intent) { - auto none_location = Location {.mode = LocationMode::None, .value = std::nullopt}; - - /* - * Sequence: 0 1 2 3 4 [5] [6] ... - ^ - Largest Sequence - RelativePrevious Value: 4 3 2 1 0 - RelativeNext Value: 0 1 ... - */ - switch (intent) { - case SubscribeIntent::immediate: - return std::make_tuple(Location{.mode=LocationMode::RelativePrevious, .value=0}, //StartGroup - none_location, // EndGroup - Location{.mode=LocationMode::RelativePrevious, .value=0}, //StartObject - none_location); // EndObject - case SubscribeIntent::sync_up: - throw std::runtime_error("Intent Unsupported for Subscribe"); - case SubscribeIntent::wait_up: - throw std::runtime_error("Intent Unsupported for Subscribe"); - default: - throw std::runtime_error("Bad Intent for Subscribe"); - } - -} - // Setup diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index f0094cff..73422316 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -116,4 +116,154 @@ TEST_CASE("AnnounceCancel Message encode/decode") MoqAnnounceCancel announce_cancel_out; CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL), announce_cancel_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_cancel_out.track_namespace); +} + +TEST_CASE("Subscribe (LatestObject) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::LatestObject; + subscribe.num_params = 0; + + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); + CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.num_params, subscribe_out.num_params); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); +} + +TEST_CASE("Subscribe (LatestGroup) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::LatestGroup; + subscribe.num_params = 0; + + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); + CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.num_params, subscribe_out.num_params); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); +} + +TEST_CASE("Subscribe (AbsoluteStart) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::AbsoluteStart; + subscribe.start_group = 0x1000; + subscribe.start_object = 0xFF; + subscribe.num_params = 0; + + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); + CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.num_params, subscribe_out.num_params); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); + CHECK_EQ(subscribe.start_group, subscribe_out.start_group); + CHECK_EQ(subscribe.start_object, subscribe_out.start_object); +} + +TEST_CASE("Subscribe (AbsoluteRange) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::AbsoluteRange; + subscribe.start_group = 0x1000; + subscribe.start_object = 0x1; + subscribe.end_group = 0xFFF; + subscribe.end_object = 0xFF; + + subscribe.num_params = 0; + + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); + CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.num_params, subscribe_out.num_params); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); + CHECK_EQ(subscribe.start_group, subscribe_out.start_group); + CHECK_EQ(subscribe.start_object, subscribe_out.start_object); + CHECK_EQ(subscribe.end_group, subscribe_out.end_group); + CHECK_EQ(subscribe.end_object, subscribe_out.end_object); +} + +TEST_CASE("Subscribe (Params) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + MoqParameter param; + param.param_type = static_cast(ParameterType::AuthorizationInfo), + param.param_length = 0x2; + param.param_value = {0x1, 0x2}; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::LatestObject; + subscribe.num_params = 1; + subscribe.track_params.push_back(param); + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); + CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); + CHECK_EQ(subscribe.track_params.size(), subscribe_out.track_params.size()); + CHECK_EQ(subscribe.track_params[0].param_type, subscribe_out.track_params[0].param_type); + CHECK_EQ(subscribe.track_params[0].param_length, subscribe_out.track_params[0].param_length); + CHECK_EQ(subscribe.track_params[0].param_value, subscribe_out.track_params[0].param_value); } \ No newline at end of file From 6dfee82fd67f7fb90ee55d511ee0c0cf97b5fabe Mon Sep 17 00:00:00 2001 From: suhasHere Date: Sun, 2 Jun 2024 09:11:24 -0700 Subject: [PATCH 10/71] add subscribe_ok --- include/quicr/moq_messages.h | 19 +++---- src/moq_messages.cpp | 102 ++++++++++++++++++++++++++--------- test/moq_messages.cpp | 45 ++++++++++++++++ 3 files changed, 132 insertions(+), 34 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index a81631a5..326759df 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -18,7 +18,7 @@ using ReasonPhrase = quicr::bytes; using GroupId = uintVar_t; using ObjectId = uintVar_t; using ObjectPriority = uintVar_t; -using SubscribeId = uintVar_t; +using SubscribeId = uint64_t; using TrackAlias = uintVar_t; using ParamType = uintVar_t; @@ -125,10 +125,16 @@ struct MoqSubscribe { struct MoqSubscribeOk { SubscribeId subscribe_id; - uintVar_t expires; + uint64_t expires; bool content_exists; - std::optional largest_group; - std::optional largest_object; + uint64_t largest_group {0}; + uint64_t largest_object {0}; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeOk& msg); +private: + size_t current_pos {0}; + const size_t MAX_FIELDS = 5; }; @@ -153,19 +159,14 @@ struct MoqSubscribeDone ObjectId final_object_id; }; -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribe &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribe &msg); MessageBuffer& operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg); -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeOk &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeOk &msg); MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeError &msg); MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeDone &msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeDone &msg); - // // Announce // diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 952e5558..a5a7bf0a 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -247,39 +247,91 @@ operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg) { return buffer; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqSubscribeOk &msg) { - buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_OK); - buffer << msg.subscribe_id; - buffer << msg.expires; - buffer << static_cast(msg.content_exists); - if (msg.content_exists) { - buffer << msg.largest_group; - buffer << msg.largest_object; - return buffer; + +/* + * SubscribeId subscribe_id; +uintVar_t expires; +bool content_exists; +std::optional largest_group; +std::optional largest_object; + + */ +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeOk& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_OK))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.expires)); + msg.content_exists ? buffer.push(static_cast(1)) : buffer.push(static_cast(0)); + if(msg.content_exists) { + buffer.push(qtransport::to_uintV(msg.largest_group)); + buffer.push(qtransport::to_uintV(msg.largest_object)); } return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqSubscribeOk &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.expires; - uint8_t content_exists {0}; - buffer >> content_exists; - if(content_exists > 1) { - throw std::runtime_error("Invalid Context Exists Value"); +bool parse_uintV_field(qtransport::StreamBuffer &buffer, uint64_t& field) { + auto val = buffer.decode_uintV(); + if (!val) { + return false; } + field = val.value(); + return true; +} - if (content_exists == 1) { - msg.content_exists = true; - buffer >> msg.largest_group; - buffer >> msg.largest_object; - return buffer; +bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) { + + switch (msg.current_pos) { + case 0: + { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.expires)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + const auto val = buffer.front(); + if (!val) { + return false; + } + buffer.pop(); + msg.content_exists = (val.value()) == 1; + msg.current_pos += 1; + if (!msg.content_exists) { + // nothing more to process. + return true; + } + } + break; + case 3: { + if(!parse_uintV_field(buffer, msg.largest_group)) { + return false; + } + msg.current_pos += 1; + } + break; + case 4: { + if(!parse_uintV_field(buffer, msg.largest_object)) { + return false; + } + msg.current_pos += 1; + } + break; + default: + throw std::runtime_error("Malformed Message (SubscribeOK)"); } - msg.content_exists = false; - return buffer; + if (msg.current_pos < msg.MAX_FIELDS) { + return false; + } + return true; } MessageBuffer & diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 73422316..ebf07541 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -266,4 +266,49 @@ TEST_CASE("Subscribe (Params) Message encode/decode") CHECK_EQ(subscribe.track_params[0].param_type, subscribe_out.track_params[0].param_type); CHECK_EQ(subscribe.track_params[0].param_length, subscribe_out.track_params[0].param_length); CHECK_EQ(subscribe.track_params[0].param_value, subscribe_out.track_params[0].param_value); +} + + +TEST_CASE("SubscribeOk Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe_ok = MoqSubscribeOk {}; + subscribe_ok.subscribe_id = 0x1; + subscribe_ok.expires = 0x100; + subscribe_ok.content_exists = false; + buffer << subscribe_ok; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribeOk subscribe_ok_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_OK), subscribe_ok_out)); + CHECK_EQ(subscribe_ok.subscribe_id, subscribe_ok_out.subscribe_id); + CHECK_EQ(subscribe_ok.expires, subscribe_ok_out.expires); + CHECK_EQ(subscribe_ok.content_exists, subscribe_ok_out.content_exists); +} + + +TEST_CASE("SubscribeOk (content-exists) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe_ok = MoqSubscribeOk {}; + subscribe_ok.subscribe_id = 0x1; + subscribe_ok.expires = 0x100; + subscribe_ok.content_exists = true; + subscribe_ok.largest_group = 0x1000; + subscribe_ok.largest_object = 0xff; + buffer << subscribe_ok; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribeOk subscribe_ok_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_OK), subscribe_ok_out)); + CHECK_EQ(subscribe_ok.subscribe_id, subscribe_ok_out.subscribe_id); + CHECK_EQ(subscribe_ok.expires, subscribe_ok_out.expires); + CHECK_EQ(subscribe_ok.content_exists, subscribe_ok_out.content_exists); + CHECK_EQ(subscribe_ok.largest_group, subscribe_ok_out.largest_group); + CHECK_EQ(subscribe_ok.largest_object, subscribe_ok_out.largest_object); + } \ No newline at end of file From 6fd6e534f149ad849eb575d5b1b47c06605e0ba8 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Sun, 2 Jun 2024 23:11:57 -0700 Subject: [PATCH 11/71] rewrite subscribe parsing --- include/quicr/moq_messages.h | 16 ++- src/moq_messages.cpp | 260 +++++++++++++++++------------------ test/moq_messages.cpp | 66 +++++++-- 3 files changed, 191 insertions(+), 151 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 326759df..6d4e0021 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -105,22 +105,24 @@ enum struct FilterType: uint64_t { }; struct MoqSubscribe { - std::optional subscribe_id; - std::optional track_alias; + uint64_t subscribe_id; + uint64_t track_alias; TrackNamespace track_namespace; TrackName track_name; FilterType filter_type {FilterType::None}; - std::optional start_group; - std::optional end_group; - std::optional start_object; - std::optional end_object; + uint64_t start_group {0}; + uint64_t end_group {0}; + uint64_t start_object {0}; + uint64_t end_object {0}; uint64_t num_params {0}; std::vector track_params; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribe& msg); private: - MoqParameter current_param{}; + std::optional current_param{}; + size_t current_pos {0}; + bool parsing_completed { false }; }; struct MoqSubscribeOk { diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index a5a7bf0a..8226f9c2 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -4,6 +4,28 @@ namespace quicr::messages { +// +// Utility +// +bool parse_uintV_field(qtransport::StreamBuffer &buffer, uint64_t& field) { + auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + field = val.value(); + return true; +} + + +bool parse_bytes_field(qtransport::StreamBuffer &buffer, quicr::bytes& field) { + auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + field = std::move(val.value()); + return true; +} + // // Optional // @@ -56,8 +78,8 @@ MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter ¶m) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribe& msg){ buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE))); - buffer.push(qtransport::to_uintV(msg.subscribe_id.value())); - buffer.push(qtransport::to_uintV(msg.track_alias.value())); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push_lv(msg.track_namespace); buffer.push_lv(msg.track_name); buffer.push(qtransport::to_uintV(static_cast(msg.filter_type))); @@ -68,15 +90,15 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& case FilterType::LatestObject: break; case FilterType::AbsoluteStart: { - buffer.push(qtransport::to_uintV(msg.start_group.value())); - buffer.push(qtransport::to_uintV(msg.start_object.value())); + buffer.push(qtransport::to_uintV(msg.start_group)); + buffer.push(qtransport::to_uintV(msg.start_object)); } break; case FilterType::AbsoluteRange: - buffer.push(qtransport::to_uintV(msg.start_group.value())); - buffer.push(qtransport::to_uintV(msg.start_object.value())); - buffer.push(qtransport::to_uintV(msg.end_group.value())); - buffer.push(qtransport::to_uintV(msg.end_object.value())); + buffer.push(qtransport::to_uintV(msg.start_group)); + buffer.push(qtransport::to_uintV(msg.start_object)); + buffer.push(qtransport::to_uintV(msg.end_group)); + buffer.push(qtransport::to_uintV(msg.end_object)); break; } @@ -92,143 +114,130 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { - if (!msg.subscribe_id.has_value()) { - auto val = buffer.decode_uintV(); - if (!val) { - return false; + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; } - msg.subscribe_id = val.value(); - } - - if (!msg.track_alias.has_value()) { - auto val = buffer.decode_uintV(); - if (!val) { - return false; + break; + case 1: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; } - msg.track_alias = val.value(); - } - - if (msg.track_namespace.empty()) - { - const auto val = buffer.decode_bytes(); - if (!val) { - return false; + break; + case 2: { + if(!parse_bytes_field(buffer, msg.track_namespace)) { + return false; + } + msg.current_pos += 1; } - msg.track_namespace = val.value(); - } - - if (msg.track_name.empty()) - { - const auto val = buffer.decode_bytes(); - if (!val) { - return false; + break; + case 3: { + if(!parse_bytes_field(buffer, msg.track_name)) { + return false; + } + msg.current_pos += 1; } - msg.track_name = val.value(); - } - - if (msg.filter_type == FilterType::None) - { - const auto val = buffer.decode_uintV(); - if (!val) { - return false; + break; + case 4: { + const auto val = buffer.decode_uintV(); + if (!val) { + return false; + } + auto filter = val.value(); + msg.filter_type = static_cast(filter); + if (msg.filter_type == FilterType::LatestGroup + || msg.filter_type == FilterType::LatestObject) { + // we don't get further fields until parameters + msg.current_pos = 9; + } else { + msg.current_pos += 1; + } } - - auto filter = val.value(); - msg.filter_type = static_cast(filter); - } - - switch (msg.filter_type) { - case FilterType::None: - throw std::runtime_error("Malformed Filter Type"); - case FilterType::LatestGroup: - case FilterType::LatestObject: - break; - case FilterType::AbsoluteStart: - { - if (!msg.start_group.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + break; + case 5: { + if (msg.filter_type == FilterType::AbsoluteStart + || msg.filter_type == FilterType::AbsoluteRange) { + if (!parse_uintV_field(buffer, msg.start_group)) { return false; } - msg.start_group = val.value(); + msg.current_pos += 1; } - - if (!msg.start_object.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + } + break; + case 6: { + if (msg.filter_type == FilterType::AbsoluteStart + || msg.filter_type == FilterType::AbsoluteRange) { + if (!parse_uintV_field(buffer, msg.start_object)) { return false; } - msg.start_object = val.value(); + + if (msg.filter_type == FilterType::AbsoluteStart) { + msg.current_pos = 9; + } else { + msg.current_pos += 1; + } } } - break; - case FilterType::AbsoluteRange: - { - if (!msg.start_group.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + break; + case 7: { + if (msg.filter_type == FilterType::AbsoluteRange) { + if (!parse_uintV_field(buffer, msg.end_group)) { return false; } - msg.start_group = val.value(); + msg.current_pos += 1; } - - if (!msg.start_object.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + } + break; + case 8: { + if (msg.filter_type == FilterType::AbsoluteRange) { + if (!parse_uintV_field(buffer, msg.end_object)) { return false; } - msg.start_object = val.value(); + msg.current_pos += 1; } - - if (!msg.end_group.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + } + break; + case 9: { + if (!msg.current_param.has_value()) { + if (!parse_uintV_field(buffer, msg.num_params)) { return false; } - msg.end_group = val.value(); + msg.current_param = MoqParameter{}; } + // parse each param + while (msg.num_params > 0) { + if (!msg.current_param.value().param_type) { + auto val = buffer.front(); + if (!val) { + return false; + } + msg.current_param.value().param_type = *val; + buffer.pop(); + } - if (!msg.end_object.has_value()) { - const auto val = buffer.decode_uintV(); - if (!val) { + // decode param_len: + auto param = buffer.decode_bytes(); + if (!param) { return false; } - msg.end_object = val.value(); + msg.current_param.value().param_length = param->size(); + msg.current_param.value().param_value = param.value(); + msg.track_params.push_back(msg.current_param.value()); + msg.current_param = MoqParameter{}; + msg.num_params -= 1; } + msg.parsing_completed = true; } - break; - } - - if (!msg.num_params) { - const auto val = buffer.decode_uintV(); - if (!val) { - return false; - } - msg.num_params = val.value(); + break; } - // parse each param - while (msg.num_params > 0) { - if (!msg.current_param.param_type) { - auto val = buffer.front(); - if (!val) { - return false; - } - msg.current_param.param_type = *val; - buffer.pop(); - } - - // decode param_len: - auto param = buffer.decode_bytes(); - if (!param) { - return false; - } - - msg.current_param.param_length = param->size(); - msg.current_param.param_value = param.value(); - msg.track_params.push_back(msg.current_param); - msg.current_param = {}; - msg.num_params -= 1; + if (!msg.parsing_completed ) { + return false; } return true; @@ -247,15 +256,6 @@ operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg) { return buffer; } - -/* - * SubscribeId subscribe_id; -uintVar_t expires; -bool content_exists; -std::optional largest_group; -std::optional largest_object; - - */ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribeOk& msg){ buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_OK))); @@ -269,14 +269,6 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& return buffer; } -bool parse_uintV_field(qtransport::StreamBuffer &buffer, uint64_t& field) { - auto val = buffer.decode_uintV(); - if (!val) { - return false; - } - field = val.value(); - return true; -} bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) { diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index ebf07541..5d5f2a15 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -138,8 +138,8 @@ TEST_CASE("Subscribe (LatestObject) Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); - CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); - CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); CHECK_EQ(subscribe.num_params, subscribe_out.num_params); CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); } @@ -164,8 +164,8 @@ TEST_CASE("Subscribe (LatestGroup) Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); - CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); - CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); CHECK_EQ(subscribe.num_params, subscribe_out.num_params); CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); } @@ -192,8 +192,8 @@ TEST_CASE("Subscribe (AbsoluteStart) Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); - CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); - CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); CHECK_EQ(subscribe.num_params, subscribe_out.num_params); CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); CHECK_EQ(subscribe.start_group, subscribe_out.start_group); @@ -225,8 +225,8 @@ TEST_CASE("Subscribe (AbsoluteRange) Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); - CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); - CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); CHECK_EQ(subscribe.num_params, subscribe_out.num_params); CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); CHECK_EQ(subscribe.start_group, subscribe_out.start_group); @@ -259,8 +259,8 @@ TEST_CASE("Subscribe (Params) Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); - CHECK_EQ(subscribe.subscribe_id.value(), subscribe_out.subscribe_id.value()); - CHECK_EQ(subscribe.track_alias.value(), subscribe_out.track_alias.value()); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); CHECK_EQ(subscribe.track_params.size(), subscribe_out.track_params.size()); CHECK_EQ(subscribe.track_params[0].param_type, subscribe_out.track_params[0].param_type); @@ -269,6 +269,52 @@ TEST_CASE("Subscribe (Params) Message encode/decode") } +TEST_CASE("Subscribe (Params - 2) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + MoqParameter param1 { + .param_type = static_cast(ParameterType::AuthorizationInfo), + .param_length = 0x2, + .param_value = {0x1, 0x2} + }; + + MoqParameter param2 { + .param_type = static_cast(ParameterType::AuthorizationInfo), + .param_length = 0x4, + .param_value = {0x1, 0x2, 0x3, 0x4} + }; + + auto subscribe = MoqSubscribe {}; + subscribe.subscribe_id = 0x1; + subscribe.track_alias = TRACK_ALIAS_ALICE_VIDEO; + subscribe.track_namespace = TRACK_NAMESPACE_CONF; + subscribe.track_name = TRACK_NAME_ALICE_VIDEO; + subscribe.filter_type = FilterType::LatestObject; + subscribe.num_params = 2; + subscribe.track_params.push_back(param1); + subscribe.track_params.push_back(param2); + buffer << subscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribe subscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribe.track_alias, subscribe_out.track_alias); + CHECK_EQ(subscribe.filter_type, subscribe_out.filter_type); + CHECK_EQ(subscribe.track_params.size(), subscribe_out.track_params.size()); + CHECK_EQ(subscribe.track_params[0].param_type, subscribe_out.track_params[0].param_type); + CHECK_EQ(subscribe.track_params[0].param_length, subscribe_out.track_params[0].param_length); + CHECK_EQ(subscribe.track_params[0].param_value, subscribe_out.track_params[0].param_value); + + CHECK_EQ(subscribe.track_params[1].param_type, subscribe_out.track_params[1].param_type); + CHECK_EQ(subscribe.track_params[1].param_length, subscribe_out.track_params[1].param_length); + CHECK_EQ(subscribe.track_params[1].param_value, subscribe_out.track_params[1].param_value); +} + + TEST_CASE("SubscribeOk Message encode/decode") { qtransport::StreamBuffer buffer; From 27039e2dcc7e49066f7472e0310844761be58da0 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 01:51:19 -0700 Subject: [PATCH 12/71] add a combo test for subscribe --- test/moq_messages.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 5d5f2a15..994204f3 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -315,6 +315,82 @@ TEST_CASE("Subscribe (Params - 2) Message encode/decode") } +MoqSubscribe generate_subscribe(FilterType filter, size_t num_params = 0, uint64_t sg = 0, uint64_t so=0, + uint64_t eg=0, uint64_t eo=0) { + MoqSubscribe out; + out.subscribe_id = 0xABCD; + out.track_alias = TRACK_ALIAS_ALICE_VIDEO; + out.track_namespace = TRACK_NAMESPACE_CONF; + out.track_name = TRACK_NAME_ALICE_VIDEO; + out.filter_type = filter; + out.num_params = num_params; + switch (filter) { + case FilterType::LatestObject: + case FilterType::LatestGroup: + break; + case FilterType::AbsoluteStart: + out.start_group = sg; + out.start_object = so; + break; + case FilterType::AbsoluteRange: + out.start_group = sg; + out.start_object = so; + out.end_group = eg; + out.end_object = eo; + break; + } + + while(num_params > 0) { + MoqParameter param1 { + .param_type = static_cast(ParameterType::AuthorizationInfo), + .param_length = 0x2, + .param_value = {0x1, 0x2} + }; + out.track_params.push_back(param1); + num_params--; + } + return out; +} + +TEST_CASE("Subscribe (Combo) Message encode/decode") +{ + auto subscribes = std::vector { + generate_subscribe(FilterType::LatestObject), + generate_subscribe(FilterType::LatestGroup), + generate_subscribe(FilterType::LatestObject, 1), + generate_subscribe(FilterType::LatestGroup, 2), + generate_subscribe(FilterType::AbsoluteStart, 0, 0x100, 0x2), + generate_subscribe(FilterType::AbsoluteStart, 2, 0x100, 0x2), + generate_subscribe(FilterType::AbsoluteRange, 0, 0x100, 0x2, 0x500, 0x2), + generate_subscribe(FilterType::AbsoluteRange, 2, 0x100, 0x2, 0x500, 0x2), + }; + + for (size_t i = 0; i < subscribes.size(); i++) { + qtransport::StreamBuffer buffer; + buffer << subscribes[i]; + std::vector net_data = buffer.front(buffer.size()); + MoqSubscribe subscribe_out; + CHECK(verify( + net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); + CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); + CHECK_EQ(subscribes[i].subscribe_id, subscribe_out.subscribe_id); + CHECK_EQ(subscribes[i].track_alias, subscribe_out.track_alias); + CHECK_EQ(subscribes[i].filter_type, subscribe_out.filter_type); + CHECK_EQ(subscribes[i].track_params.size(), subscribe_out.track_params.size()); + for(size_t j = 0; j < subscribes[i].track_params.size(); j++) { + CHECK_EQ(subscribes[i].track_params[j].param_type, + subscribe_out.track_params[j].param_type); + CHECK_EQ(subscribes[i].track_params[j].param_length, + subscribe_out.track_params[j].param_length); + CHECK_EQ(subscribes[i].track_params[j].param_value, + subscribe_out.track_params[j].param_value); + + } + } +} + + TEST_CASE("SubscribeOk Message encode/decode") { qtransport::StreamBuffer buffer; From 4bc13c3ca47a164fe304f82e669ea22c2920d299 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 02:04:26 -0700 Subject: [PATCH 13/71] add subscribe error encode/decode/test --- include/quicr/moq_messages.h | 12 ++++++-- src/moq_messages.cpp | 54 ++++++++++++++++++++++++++++++++++++ test/moq_messages.cpp | 20 +++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 6d4e0021..c547c98a 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -136,15 +136,21 @@ struct MoqSubscribeOk { const MoqSubscribeOk& msg); private: size_t current_pos {0}; - const size_t MAX_FIELDS = 5; + const size_t MAX_FIELDS {5}; }; struct MoqSubscribeError { - SubscribeId subscribe_id; + uint64_t subscribe_id; ErrorCode err_code; ReasonPhrase reason_phrase; - TrackAlias track_alias; + uint64_t track_alias; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeError& msg); +private: + size_t current_pos {0}; + const size_t MAX_FIELDS {4}; }; struct MoqUnsubscribe { diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 8226f9c2..a2ede675 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -326,6 +326,60 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) return true; } + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeError& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.err_code)); + buffer.push_lv(msg.reason_phrase); + buffer.push(qtransport::to_uintV(msg.track_alias)); + return buffer; +} + + +bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &msg) { + + switch (msg.current_pos) { + case 0: + { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.err_code)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.reason_phrase = std::move(val.value()); + msg.current_pos += 1; + } + break; + case 3: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; + } + break; + } + + if (msg.current_pos < msg.MAX_FIELDS) { + return false; + } + return true; +} + MessageBuffer & operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg) { buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR); diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 994204f3..48e03a56 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -432,5 +432,25 @@ TEST_CASE("SubscribeOk (content-exists) Message encode/decode") CHECK_EQ(subscribe_ok.content_exists, subscribe_ok_out.content_exists); CHECK_EQ(subscribe_ok.largest_group, subscribe_ok_out.largest_group); CHECK_EQ(subscribe_ok.largest_object, subscribe_ok_out.largest_object); +} + +TEST_CASE("SubscribeError Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe_err = MoqSubscribeError {}; + subscribe_err.subscribe_id = 0x1; + subscribe_err.err_code = 0; + subscribe_err.reason_phrase = quicr::bytes {0x0, 0x1}; + subscribe_err.track_alias = TRACK_ALIAS_ALICE_VIDEO; + buffer << subscribe_err; + + std::vector net_data = buffer.front(buffer.size()); + MoqSubscribeError subscribe_err_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR), subscribe_err_out)); + CHECK_EQ(subscribe_err.subscribe_id, subscribe_err_out.subscribe_id); + CHECK_EQ(subscribe_err.err_code, subscribe_err_out.err_code); + CHECK_EQ(subscribe_err.reason_phrase, subscribe_err_out.reason_phrase); + CHECK_EQ(subscribe_err.track_alias, subscribe_err_out.track_alias); } \ No newline at end of file From 42a2d419fd3817ac556abe1b0d32984709c9cf81 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 03:53:17 -0700 Subject: [PATCH 14/71] add subscribedone and unsunbscribe encode/decode/test --- include/quicr/moq_messages.h | 17 +++- src/moq_messages.cpp | 150 +++++++++++++++++++++-------------- test/moq_messages.cpp | 63 +++++++++++++++ 3 files changed, 165 insertions(+), 65 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index c547c98a..ec2f950c 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -155,16 +155,25 @@ struct MoqSubscribeError { struct MoqUnsubscribe { SubscribeId subscribe_id; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqUnsubscribe &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqUnsubscribe& msg); }; struct MoqSubscribeDone { - SubscribeId subscribe_id; - StatusCode status_code; + uint64_t subscribe_id; + uint64_t status_code; ReasonPhrase reason_phrase; bool content_exists; - GroupId final_group_id; - ObjectId final_object_id; + uint64_t final_group_id; + uint64_t final_object_id; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeDone& msg); +private: + size_t current_pos {0}; + const size_t MAX_FIELDS = {6}; }; MessageBuffer& operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index a2ede675..30a523bc 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -243,19 +243,101 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { return true; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg) { - buffer << static_cast(MESSAGE_TYPE_UNSUBSCRIBE); - buffer << msg.subscribe_id; + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqUnsubscribe& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_UNSUBSCRIBE))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg) { - buffer >> msg.subscribe_id; + +bool operator>>(qtransport::StreamBuffer &buffer, MoqUnsubscribe &msg) { + + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + return true; +} + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqSubscribeDone& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.status_code)); + buffer.push_lv(msg.reason_phrase); + msg.content_exists ? buffer.push(static_cast(1)) : buffer.push(static_cast(0)); + if(msg.content_exists) { + buffer.push(qtransport::to_uintV(msg.final_group_id)); + buffer.push(qtransport::to_uintV(msg.final_object_id)); + } + return buffer; } + +bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.status_code)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.reason_phrase = std::move(val.value()); + msg.current_pos += 1; + } + break; + case 3: { + const auto val = buffer.front(); + if (!val) { + return false; + } + buffer.pop(); + msg.content_exists = (val.value()) == 1; + msg.current_pos += 1; + if (!msg.content_exists) { + // nothing more to process. + return true; + } + } + break; + case 4: { + if(!parse_uintV_field(buffer, msg.final_group_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 5: { + if(!parse_uintV_field(buffer, msg.final_object_id)) { + return false; + } + msg.current_pos += 1; + } + break; + } + + if (msg.current_pos < msg.MAX_FIELDS) { + return false; + } + return true; +} + qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribeOk& msg){ buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_OK))); @@ -316,8 +398,6 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) msg.current_pos += 1; } break; - default: - throw std::runtime_error("Malformed Message (SubscribeOK)"); } if (msg.current_pos < msg.MAX_FIELDS) { @@ -380,60 +460,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &ms return true; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg) { - buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR); - buffer << msg.subscribe_id; - buffer << msg.err_code; - buffer << msg.reason_phrase; - buffer << msg.track_alias; - return buffer; -} -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqSubscribeError &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.err_code; - buffer >> msg.reason_phrase; - buffer >> msg.track_alias; - return buffer; -} -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqSubscribeDone &msg) { - buffer << static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE); - buffer << msg.subscribe_id; - buffer << msg.status_code; - buffer << msg.reason_phrase; - buffer << uint8_t(msg.content_exists); - if (msg.content_exists) { - buffer << msg.final_group_id; - buffer << msg.final_object_id; - } - return buffer; -} - -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqSubscribeDone&msg) { - buffer >> msg.subscribe_id; - buffer >> msg.status_code; - buffer >> msg.reason_phrase; - uint8_t context_exists {0}; - buffer >> context_exists; - if (context_exists > 1) { - throw std::runtime_error("Incorrect Context Exists value"); - } - - if (context_exists == 1) { - msg.content_exists = true; - buffer >> msg.final_group_id; - buffer >> msg.final_object_id; - return buffer; - } - - msg.content_exists = false; - return buffer; -} // diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 48e03a56..270ca23e 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -453,4 +453,67 @@ TEST_CASE("SubscribeError Message encode/decode") CHECK_EQ(subscribe_err.err_code, subscribe_err_out.err_code); CHECK_EQ(subscribe_err.reason_phrase, subscribe_err_out.reason_phrase); CHECK_EQ(subscribe_err.track_alias, subscribe_err_out.track_alias); +} + +TEST_CASE("Unsubscribe Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto unsubscribe = MoqUnsubscribe {}; + unsubscribe.subscribe_id = 0x1; + buffer << unsubscribe; + + std::vector net_data = buffer.front(buffer.size()); + + MoqUnsubscribe unsubscribe_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_UNSUBSCRIBE), unsubscribe_out)); + CHECK_EQ(unsubscribe.subscribe_id, unsubscribe_out.subscribe_id); +} + +TEST_CASE("SubscribeDone Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe_done = MoqSubscribeDone {}; + subscribe_done.subscribe_id = 0x1; + subscribe_done.status_code = 0x0; + subscribe_done.reason_phrase = quicr::bytes {0x0}; + subscribe_done.content_exists = false; + + buffer << subscribe_done; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribeDone subscribe_done_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE), subscribe_done_out)); + CHECK_EQ(subscribe_done.subscribe_id, subscribe_done_out.subscribe_id); + CHECK_EQ(subscribe_done.status_code, subscribe_done_out.status_code); + CHECK_EQ(subscribe_done.reason_phrase, subscribe_done_out.reason_phrase); + CHECK_EQ(subscribe_done.content_exists, subscribe_done_out.content_exists); +} + +TEST_CASE("SubscribeDone (content-exists) Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto subscribe_done = MoqSubscribeDone {}; + subscribe_done.subscribe_id = 0x1; + subscribe_done.status_code = 0x0; + subscribe_done.reason_phrase = quicr::bytes {0x0}; + subscribe_done.content_exists = true; + subscribe_done.final_group_id = 0x1111; + subscribe_done.final_object_id = 0xff; + + buffer << subscribe_done; + + std::vector net_data = buffer.front(buffer.size()); + + MoqSubscribeDone subscribe_done_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE), subscribe_done_out)); + CHECK_EQ(subscribe_done.subscribe_id, subscribe_done_out.subscribe_id); + CHECK_EQ(subscribe_done.status_code, subscribe_done_out.status_code); + CHECK_EQ(subscribe_done.reason_phrase, subscribe_done_out.reason_phrase); + CHECK_EQ(subscribe_done.content_exists, subscribe_done_out.content_exists); + CHECK_EQ(subscribe_done.final_group_id, subscribe_done_out.final_group_id); + CHECK_EQ(subscribe_done.final_object_id, subscribe_done_out.final_object_id); } \ No newline at end of file From 6b8baa7f1e86a445b685cadad07c25400aa25480 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 11:55:46 -0700 Subject: [PATCH 15/71] wip client setup --- include/quicr/moq_messages.h | 33 +++++--- src/moq_messages.cpp | 141 +++++++++++++++++++++++++---------- test/moq_messages.cpp | 49 ++++++++---- 3 files changed, 156 insertions(+), 67 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index ec2f950c..794ea72e 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -9,7 +9,7 @@ namespace quicr::messages { -using Version = uintVar_t; +using Version = uint64_t; using TrackNamespace = quicr::bytes; using TrackName = quicr::bytes; using ErrorCode = uint64_t; @@ -64,10 +64,15 @@ enum struct ParameterType : uint8_t { Invalid = 0xFF, // used internally. }; -struct MoqParameter{ - std::optional param_type; - uintVar_t param_length; +struct MoqParameter { + uint64_t param_type {0}; + uint64_t param_length {0}; bytes param_value; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqParameter &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqParameter& msg); +private: + uint64_t current_pos {0}; }; // @@ -75,15 +80,27 @@ struct MoqParameter{ // struct MoqClientSetup { + uint64_t num_versions {0}; std::vector supported_versions; MoqParameter role_parameter; MoqParameter path_parameter; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqClientSetup& msg); +private: + size_t current_pos {0}; + uint64_t num_params {0}; + MoqParameter current_param {}; + bool parse_completed { false }; }; struct MoqServerSetup { Version supported_version; MoqParameter role_parameter; MoqParameter path_parameter; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqServerSetup& msg); }; MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter &msg); @@ -176,14 +193,6 @@ struct MoqSubscribeDone const size_t MAX_FIELDS = {6}; }; -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqUnsubscribe &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqUnsubscribe &msg); -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeError &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeError &msg); -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqSubscribeDone &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqSubscribeDone &msg); - - // // Announce // diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 30a523bc..52c389e3 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -51,6 +51,40 @@ MessageBuffer& operator>>(MessageBuffer& buffer, std::optional& val) { // MoqParameter // +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqParameter& param){ + + buffer.push(qtransport::to_uintV(param.param_type)); + buffer.push(qtransport::to_uintV(param.param_length)); + if (param.param_length) { + buffer.push_lv(param.param_value); + } + return buffer; +} + + +bool operator>>(qtransport::StreamBuffer &buffer, MoqParameter ¶m) { + + if(!parse_uintV_field(buffer, param.param_type)) { + return false; + } + + if(!parse_uintV_field(buffer, param.param_length)) { + return false; + } + + if(param.param_length) { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + param.param_value = std::move(val.value()); + } + + return true; +} + + MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter ¶m) { buffer << param.param_type; buffer << param.param_length; @@ -104,7 +138,7 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer.push(qtransport::to_uintV(msg.num_params)); for (const auto& param: msg.track_params) { - buffer.push(qtransport::to_uintV(static_cast(param.param_type.value()))); + buffer.push(qtransport::to_uintV(static_cast(param.param_type))); buffer.push(qtransport::to_uintV(param.param_length)); buffer.push(param.param_value); } @@ -763,59 +797,86 @@ operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg) { } -// Setup +// Client Setup message +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqClientSetup& msg){ -// vector encode/decode -MessageBuffer& -operator<<(MessageBuffer& buffer, const std::vector& val) -{ - buffer << static_cast(val.size()); - for(uint64_t i = 0; i < val.size(); i++) { - buffer << val[i]; + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_CLIENT_SETUP))); + buffer.push(qtransport::to_uintV(static_cast(msg.supported_versions.size()))); + // versions + for (const auto& ver: msg.supported_versions) { + buffer.push(qtransport::to_uintV(ver)); } + /// num params + buffer.push(qtransport::to_uintV(static_cast(1))); + // role param + buffer.push(qtransport::to_uintV(static_cast(msg.role_parameter.param_type))); + buffer.push(qtransport::to_uintV(static_cast(1))); + buffer.push_lv(msg.role_parameter.param_value); return buffer; } -MessageBuffer& -operator>>(MessageBuffer& msg, std::vector& val) -{ - auto vec_size = uintVar_t(0); - msg >> vec_size; - auto version = std::vector(); - version.resize((uint64_t) vec_size); - val.resize((uint64_t) vec_size); +bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) { + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.num_versions)) { + return false; + } + msg.current_pos += 1; + } + case 1: { + while (msg.num_versions > 0) { + uint64_t version{ 0 }; + if (!parse_uintV_field(buffer, version)) { + return false; + } + msg.supported_versions.push_back(version); + msg.num_versions -= 1; + } + msg.current_pos += 1; + } + break; + case 2: { + if(!parse_uintV_field(buffer, msg.num_params)) { + return false; + } - // TODO (Suhas): This needs revisiting - for(uint64_t i = 0; i < version.size(); i++) { - msg >> version[i]; - } - val = std::move(version); - return msg; -} + while (msg.num_params > 0) { + if (!msg.current_param.param_type) { + auto val = buffer.front(); + if (!val) { + return false; + } + msg.current_param.param_type = *val; + buffer.pop(); + } + auto param = buffer.decode_bytes(); + if (!param) { + return false; + } + msg.current_param.param_length = param->size(); + msg.current_param.param_value = param.value(); + static_cast(msg.current_param.param_type) == ParameterType::Role ? + msg.role_parameter = std::move(msg.current_param) : msg.path_parameter = std::move(msg.current_param); + msg.current_param = MoqParameter{}; + msg.num_params -= 1; + } -// Client Setup message -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqClientSetup &msg) { - buffer << static_cast(MESSAGE_TYPE_CLIENT_SETUP); - // versions - buffer << static_cast(msg.supported_versions.size()); - for (const auto& ver: msg.supported_versions) { - buffer << static_cast(ver); + msg.parse_completed = true; + } + break; } - // TODO (Suhas): Add support for PATH Param - // num params - buffer << uintVar_t{1}; - // role param - buffer << static_cast(msg.role_parameter.param_type.value()); - buffer << uintVar_t(1); - buffer << msg.role_parameter.param_value; + if (!msg.parse_completed) { + return false; + } - return buffer; + return true; } + MessageBuffer & operator>>(MessageBuffer &buffer, MoqClientSetup &msg) { uintVar_t num_versions {0}; diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 270ca23e..3ceb957e 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -272,17 +272,16 @@ TEST_CASE("Subscribe (Params) Message encode/decode") TEST_CASE("Subscribe (Params - 2) Message encode/decode") { qtransport::StreamBuffer buffer; - MoqParameter param1 { - .param_type = static_cast(ParameterType::AuthorizationInfo), - .param_length = 0x2, - .param_value = {0x1, 0x2} - }; + MoqParameter param1; + param1.param_type = static_cast(ParameterType::AuthorizationInfo); + param1.param_length = 0x2; + param1.param_value = {0x1, 0x2}; + + MoqParameter param2; + param2.param_type = static_cast(ParameterType::AuthorizationInfo); + param2.param_length = 0x3; + param2.param_value = {0x1, 0x2, 0x3}; - MoqParameter param2 { - .param_type = static_cast(ParameterType::AuthorizationInfo), - .param_length = 0x4, - .param_value = {0x1, 0x2, 0x3, 0x4} - }; auto subscribe = MoqSubscribe {}; subscribe.subscribe_id = 0x1; @@ -341,11 +340,10 @@ MoqSubscribe generate_subscribe(FilterType filter, size_t num_params = 0, uint6 } while(num_params > 0) { - MoqParameter param1 { - .param_type = static_cast(ParameterType::AuthorizationInfo), - .param_length = 0x2, - .param_value = {0x1, 0x2} - }; + MoqParameter param1; + param1.param_type = static_cast(ParameterType::AuthorizationInfo); + param1.param_length = 0x2; + param1.param_value = {0x1, 0x2}; out.track_params.push_back(param1); num_params--; } @@ -516,4 +514,25 @@ TEST_CASE("SubscribeDone (content-exists) Message encode/decode") CHECK_EQ(subscribe_done.content_exists, subscribe_done_out.content_exists); CHECK_EQ(subscribe_done.final_group_id, subscribe_done_out.final_group_id); CHECK_EQ(subscribe_done.final_object_id, subscribe_done_out.final_object_id); +} + + +TEST_CASE("ClientSetup Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto client_setup = MoqClientSetup {}; + client_setup.num_versions = 2; + client_setup.supported_versions = {0x1000, 0x2000}; + client_setup.role_parameter.param_type = static_cast(ParameterType::Role); + client_setup.role_parameter.param_length = 0x1; + client_setup.role_parameter.param_value = {0xFF}; + + buffer << client_setup; + + std::vector net_data = buffer.front(buffer.size()); + + MoqClientSetup client_setup_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_CLIENT_SETUP), client_setup_out)); + CHECK_EQ(client_setup.supported_versions, client_setup_out.supported_versions); + CHECK_EQ(client_setup.role_parameter.param_value, client_setup_out.role_parameter.param_value); } \ No newline at end of file From 2b58363b99ae009d6f9b6a9e7cded5d9f6ac326c Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 23:17:31 -0700 Subject: [PATCH 16/71] fix up client setup, hardcoded role --- include/quicr/moq_messages.h | 4 ++-- src/moq_messages.cpp | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 794ea72e..1bb3a1d0 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -89,8 +89,8 @@ struct MoqClientSetup { const MoqClientSetup& msg); private: size_t current_pos {0}; - uint64_t num_params {0}; - MoqParameter current_param {}; + std::optional num_params; + std::optional current_param {}; bool parse_completed { false }; }; diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 52c389e3..f69dc9a5 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -812,7 +812,6 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer.push(qtransport::to_uintV(static_cast(1))); // role param buffer.push(qtransport::to_uintV(static_cast(msg.role_parameter.param_type))); - buffer.push(qtransport::to_uintV(static_cast(1))); buffer.push_lv(msg.role_parameter.param_value); return buffer; } @@ -838,17 +837,21 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) } break; case 2: { - if(!parse_uintV_field(buffer, msg.num_params)) { - return false; + if(!msg.num_params.has_value()) { + auto params = uint64_t {0}; + if (!parse_uintV_field(buffer, params)) { + return false; + } + msg.num_params = params; } - while (msg.num_params > 0) { - if (!msg.current_param.param_type) { + if (!msg.current_param.has_value()) { auto val = buffer.front(); if (!val) { return false; } - msg.current_param.param_type = *val; + msg.current_param = MoqParameter{}; + msg.current_param->param_type = *val; buffer.pop(); } @@ -856,12 +859,12 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) if (!param) { return false; } - msg.current_param.param_length = param->size(); - msg.current_param.param_value = param.value(); - static_cast(msg.current_param.param_type) == ParameterType::Role ? - msg.role_parameter = std::move(msg.current_param) : msg.path_parameter = std::move(msg.current_param); + msg.current_param->param_length = param->size(); + msg.current_param->param_value = param.value(); + static_cast(msg.current_param->param_type) == ParameterType::Role ? + msg.role_parameter = std::move(msg.current_param.value()) : msg.path_parameter = std::move(msg.current_param.value()); msg.current_param = MoqParameter{}; - msg.num_params -= 1; + msg.num_params.value() -= 1; } msg.parse_completed = true; From 18f63b8314233e7df7808840b3d746d1ddfa5b6e Mon Sep 17 00:00:00 2001 From: suhasHere Date: Mon, 3 Jun 2024 23:52:36 -0700 Subject: [PATCH 17/71] add server setup --- include/quicr/moq_messages.h | 12 ++-- src/moq_messages.cpp | 108 +++++++++++++++++++---------------- test/moq_messages.cpp | 20 +++++++ 3 files changed, 86 insertions(+), 54 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 1bb3a1d0..6f5e8f94 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -95,20 +95,22 @@ struct MoqClientSetup { }; struct MoqServerSetup { - Version supported_version; + Version selection_version; MoqParameter role_parameter; MoqParameter path_parameter; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqServerSetup& msg); +private: + size_t current_pos {0}; + std::optional num_params; + bool parse_completed { false }; + std::optional current_param {}; + }; MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter &msg); MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter &msg); -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqClientSetup &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqClientSetup &msg); -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqServerSetup &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqServerSetup &msg); // // Subscribe diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index f69dc9a5..ac0163bf 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -880,61 +880,71 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqClientSetup &msg) { - uintVar_t num_versions {0}; - buffer >> num_versions; - msg.supported_versions.resize(num_versions); - for(size_t i = 0; i < num_versions; i++) { - uintVar_t version{0}; - buffer >> version; - msg.supported_versions.push_back(version); - } - - uintVar_t num_params {0}; - buffer >> num_params; - if (static_cast (num_params) == 0) { - return buffer; - } - - while (static_cast(num_params) > 0) { - uint8_t param_type {0}; - buffer >> param_type; - auto param_type_enum = static_cast (param_type); - switch(param_type_enum) { - case ParameterType::Role: { - msg.role_parameter.param_type = param_type; - buffer >> msg.role_parameter.param_length; - buffer >> msg.role_parameter.param_value; + +// Server Setup message + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqServerSetup& msg){ + + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SERVER_SETUP))); + buffer.push(qtransport::to_uintV(msg.selection_version)); + + /// num params + buffer.push(qtransport::to_uintV(static_cast(1))); + // role param + buffer.push(qtransport::to_uintV(static_cast(msg.role_parameter.param_type))); + buffer.push_lv(msg.role_parameter.param_value); + return buffer; +} + +bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) { + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.selection_version)) { + return false; } - break; - case ParameterType::Path: { - msg.path_parameter.param_type = param_type; - buffer >> msg.path_parameter.param_length; - buffer >> msg.path_parameter.param_value; + msg.current_pos += 1; + } + case 1: { + if(!msg.num_params.has_value()) { + auto params = uint64_t {0}; + if (!parse_uintV_field(buffer, params)) { + return false; + } + msg.num_params = params; } - break; - default: - throw std::runtime_error("Unsupported Parameter Type for ClientSetup"); + while (msg.num_params > 0) { + if (!msg.current_param.has_value()) { + auto val = buffer.front(); + if (!val) { + return false; + } + msg.current_param = MoqParameter{}; + msg.current_param->param_type = *val; + buffer.pop(); + } + + auto param = buffer.decode_bytes(); + if (!param) { + return false; + } + msg.current_param->param_length = param->size(); + msg.current_param->param_value = param.value(); + static_cast(msg.current_param->param_type) == ParameterType::Role ? + msg.role_parameter = std::move(msg.current_param.value()) : msg.path_parameter = std::move(msg.current_param.value()); + msg.current_param = MoqParameter{}; + msg.num_params.value() -= 1; + } + msg.parse_completed = true; } - num_params = num_params - 1; + break; } + if (!msg.parse_completed) { + return false; + } - return buffer; -} - -// Server Setup message -MessageBuffer& -operator<<(MessageBuffer &buffer, const MoqServerSetup &msg) { - buffer << static_cast(MESSAGE_TYPE_SERVER_SETUP); - buffer << msg.supported_version; - return buffer; + return true; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqServerSetup &msg) { - buffer >> msg.supported_version; - return buffer; -} } \ No newline at end of file diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 3ceb957e..49502e8e 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -535,4 +535,24 @@ TEST_CASE("ClientSetup Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_CLIENT_SETUP), client_setup_out)); CHECK_EQ(client_setup.supported_versions, client_setup_out.supported_versions); CHECK_EQ(client_setup.role_parameter.param_value, client_setup_out.role_parameter.param_value); +} + + +TEST_CASE("ServertSetup Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto server_setup = MoqServerSetup {}; + server_setup.selection_version = {0x1000}; + server_setup.role_parameter.param_type = static_cast(ParameterType::Role); + server_setup.role_parameter.param_length = 0x1; + server_setup.role_parameter.param_value = {0xFF}; + + buffer << server_setup; + + std::vector net_data = buffer.front(buffer.size()); + + MoqServerSetup server_setup_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SERVER_SETUP), server_setup_out)); + CHECK_EQ(server_setup.selection_version, server_setup_out.selection_version); + CHECK_EQ(server_setup.role_parameter.param_value, server_setup.role_parameter.param_value); } \ No newline at end of file From c7e0aea4b870d7112ead1f92745b70041b4177c9 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 09:48:13 -0700 Subject: [PATCH 18/71] add object stream encode/decode --- include/quicr/moq_messages.h | 18 ++++++---- src/moq_messages.cpp | 69 ++++++++++++++++++++++++++++++++++++ test/moq_messages.cpp | 27 +++++++++++++- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 6f5e8f94..d30a80f5 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -13,14 +13,14 @@ using Version = uint64_t; using TrackNamespace = quicr::bytes; using TrackName = quicr::bytes; using ErrorCode = uint64_t; -using StatusCode = uintVar_t; +using StatusCode = uint64_t; using ReasonPhrase = quicr::bytes; -using GroupId = uintVar_t; -using ObjectId = uintVar_t; -using ObjectPriority = uintVar_t; +using GroupId = uint64_t; +using ObjectId = uint64_t; +using ObjectPriority = uint64_t; using SubscribeId = uint64_t; -using TrackAlias = uintVar_t; -using ParamType = uintVar_t; +using TrackAlias = uint64_t; +using ParamType = uint64_t; // Ref: https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-messages constexpr uint8_t MESSAGE_TYPE_OBJECT_STREAM = 0x0; @@ -263,6 +263,12 @@ struct MoqObjectStream { ObjectId object_id; ObjectPriority priority; quicr::bytes payload; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqObjectStream& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; struct MoqObjectDatagram { diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index ac0163bf..df083dde 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -684,6 +684,75 @@ operator>>(MessageBuffer &buffer, MoqGoaway &msg) { // Object // +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqObjectStream& msg){ + + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_OBJECT_STREAM))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.track_alias)); + buffer.push(qtransport::to_uintV(msg.group_id)); + buffer.push(qtransport::to_uintV(msg.object_id)); + buffer.push(qtransport::to_uintV(msg.priority)); + buffer.push_lv(msg.payload); + return buffer; +} + +bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + if(!parse_uintV_field(buffer, msg.group_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 3: { + if(!parse_uintV_field(buffer, msg.object_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 4: { + if(!parse_uintV_field(buffer, msg.priority)) { + return false; + } + msg.current_pos += 1; + } + break; + case 5: { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.payload = std::move(val.value()); + msg.parse_completed = true; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + + return true; +} + MessageBuffer & operator<<(MessageBuffer &buffer, const MoqObjectStream &msg) { buffer << static_cast(MESSAGE_TYPE_OBJECT_STREAM); diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 49502e8e..ec116dfd 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -538,7 +538,7 @@ TEST_CASE("ClientSetup Message encode/decode") } -TEST_CASE("ServertSetup Message encode/decode") +TEST_CASE("ServerSetup Message encode/decode") { qtransport::StreamBuffer buffer; auto server_setup = MoqServerSetup {}; @@ -555,4 +555,29 @@ TEST_CASE("ServertSetup Message encode/decode") CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SERVER_SETUP), server_setup_out)); CHECK_EQ(server_setup.selection_version, server_setup_out.selection_version); CHECK_EQ(server_setup.role_parameter.param_value, server_setup.role_parameter.param_value); +} + +TEST_CASE("ObjectStream Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto object_stream = MoqObjectStream {}; + object_stream.subscribe_id = 0x100; + object_stream.track_alias = TRACK_ALIAS_ALICE_VIDEO; + object_stream.group_id = 0x1000; + object_stream.object_id = 0xFF; + object_stream.priority =0xA; + object_stream.payload = {0x1, 0x2, 0x3, 0x5, 0x6}; + + buffer << object_stream; + + std::vector net_data = buffer.front(buffer.size()); + + MoqObjectStream object_stream_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_OBJECT_STREAM), object_stream_out)); + CHECK_EQ(object_stream.subscribe_id, object_stream_out.subscribe_id); + CHECK_EQ(object_stream.track_alias, object_stream_out.track_alias); + CHECK_EQ(object_stream.group_id, object_stream_out.group_id); + CHECK_EQ(object_stream.object_id, object_stream_out.object_id); + CHECK_EQ(object_stream.priority, object_stream_out.priority); + CHECK_EQ(object_stream.payload, object_stream_out.payload); } \ No newline at end of file From d6905de5dad386779e0e5982a1272c3b789716c0 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 10:10:29 -0700 Subject: [PATCH 19/71] add encode/decode to object and friends --- include/quicr/moq_messages.h | 30 ++++ src/moq_messages.cpp | 305 +++++++++++++++++++++++++---------- 2 files changed, 249 insertions(+), 86 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index d30a80f5..7337b056 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -278,6 +278,12 @@ struct MoqObjectDatagram { ObjectId object_id; ObjectPriority priority; quicr::bytes payload; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectDatagram &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqObjectDatagram& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; @@ -285,12 +291,24 @@ struct MoqStreamHeaderTrack { SubscribeId subscribe_id; TrackAlias track_alias; ObjectPriority priority; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderTrack &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamHeaderTrack& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; struct MoqStreamTrackObject { GroupId group_id; ObjectId object_id; quicr::bytes payload; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamTrackObject& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; @@ -299,11 +317,23 @@ struct MoqStreamHeaderGroup { TrackAlias track_alias; GroupId group_id; ObjectPriority priority; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderGroup &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamHeaderGroup& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; struct MoqStreamGroupObject { ObjectId object_id; quicr::bytes payload; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamGroupObject &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamGroupObject& msg); +private: + uint64_t current_pos {0}; + bool parse_completed { false }; }; MessageBuffer& operator<<(MessageBuffer& buffer, const MoqObjectStream& msg); diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index df083dde..9315a4c5 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -753,118 +753,251 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg) return true; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqObjectStream &msg) { - buffer << static_cast(MESSAGE_TYPE_OBJECT_STREAM); - buffer << msg.subscribe_id; - buffer << msg.track_alias; - buffer << msg.group_id; - buffer << msg.object_id; - buffer << msg.priority; - buffer << msg.payload; - return buffer; -} -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqObjectStream &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.track_alias; - buffer >> msg.group_id; - buffer >> msg.object_id; - buffer >> msg.priority; - buffer >> msg.payload; - return buffer; -} +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqObjectDatagram& msg){ -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqObjectDatagram &msg) { - buffer << MESSAGE_TYPE_OBJECT_DATAGRAM; - buffer << msg.subscribe_id; - buffer << msg.track_alias; - buffer << msg.group_id; - buffer << msg.object_id; - buffer << msg.priority; - buffer << msg.payload; + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_OBJECT_DATAGRAM))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.track_alias)); + buffer.push(qtransport::to_uintV(msg.group_id)); + buffer.push(qtransport::to_uintV(msg.object_id)); + buffer.push(qtransport::to_uintV(msg.priority)); + buffer.push_lv(msg.payload); return buffer; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqObjectDatagram &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.track_alias; - buffer >> msg.group_id; - buffer >> msg.object_id; - buffer >> msg.priority; - buffer >> msg.payload; - return buffer; -} +bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectDatagram &msg) { -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqStreamTrackObject &msg) { - buffer << msg.group_id; - buffer << msg.object_id; - buffer << msg.payload; - return buffer; + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + if(!parse_uintV_field(buffer, msg.group_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 3: { + if(!parse_uintV_field(buffer, msg.object_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 4: { + if(!parse_uintV_field(buffer, msg.priority)) { + return false; + } + msg.current_pos += 1; + } + break; + case 5: { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.payload = std::move(val.value()); + msg.parse_completed = true; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + + return true; } -MessageBuffer& -operator>>(MessageBuffer &buffer, MoqStreamTrackObject &msg) { - buffer >> msg.group_id; - buffer >> msg.object_id; - buffer >> msg.payload; + + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamHeaderTrack& msg){ + + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_STREAM_HEADER_TRACK))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.track_alias)); + buffer.push(qtransport::to_uintV(msg.priority)); return buffer; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqStreamHeaderTrack &msg) { - buffer << MESSAGE_TYPE_STREAM_HEADER_TRACK; - buffer << msg.subscribe_id; - buffer << msg.track_alias; - buffer << msg.priority; - return buffer; +bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderTrack &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + if(!parse_uintV_field(buffer, msg.priority)) { + return false; + } + msg.current_pos += 1; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + return true; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqStreamHeaderTrack &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.track_alias; - buffer >> msg.priority; +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamTrackObject& msg){ + + buffer.push(qtransport::to_uintV(msg.group_id)); + buffer.push(qtransport::to_uintV(msg.object_id)); + buffer.push_lv(msg.payload); return buffer; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqStreamHeaderGroup &msg) { - buffer << MESSAGE_TYPE_STREAM_HEADER_GROUP; - buffer << msg.subscribe_id; - buffer << msg.track_alias; - buffer << msg.group_id; - buffer << msg.priority; - return buffer; +bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.group_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.object_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + const auto val = buffer.decode_bytes(); + if(!val) { + return false; + } + msg.parse_completed = true; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + return true; } -MessageBuffer & -operator>>(MessageBuffer &buffer, MoqStreamHeaderGroup &msg) { - buffer >> msg.subscribe_id; - buffer >> msg.track_alias; - buffer >> msg.group_id; - buffer >> msg.priority; + + +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamHeaderGroup& msg){ + + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_STREAM_HEADER_GROUP))); + buffer.push(qtransport::to_uintV(msg.subscribe_id)); + buffer.push(qtransport::to_uintV(msg.track_alias)); + buffer.push(qtransport::to_uintV(msg.group_id)); + buffer.push(qtransport::to_uintV(msg.priority)); return buffer; } -MessageBuffer & -operator<<(MessageBuffer &buffer, const MoqStreamGroupObject &msg) { - buffer << msg.object_id; - buffer << msg.payload; - return buffer; +bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderGroup &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.subscribe_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + if(!parse_uintV_field(buffer, msg.track_alias)) { + return false; + } + msg.current_pos += 1; + } + break; + case 2: { + if(!parse_uintV_field(buffer, msg.group_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 4: { + if(!parse_uintV_field(buffer, msg.priority)) { + return false; + } + msg.current_pos += 1; + msg.parse_completed = true; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + + return true; } -MessageBuffer& -operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg) { - buffer >> msg.object_id; - buffer >> msg.payload; +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqStreamGroupObject& msg){ + + buffer.push(qtransport::to_uintV(msg.object_id)); + buffer.push_lv(msg.payload); return buffer; } +bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamGroupObject &msg) { + + switch (msg.current_pos) { + case 0: { + if(!parse_uintV_field(buffer, msg.object_id)) { + return false; + } + msg.current_pos += 1; + } + break; + case 1: { + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.payload = std::move(val.value()); + msg.parse_completed = true; + } + break; + } + + if(!msg.parse_completed) { + return false; + } + + return true; +} // Client Setup message qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, From 5d9129526156540f5dc7d3d003295d20dc431c09 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 11:40:11 -0700 Subject: [PATCH 20/71] object tests work in progress --- src/moq_messages.cpp | 3 +- test/moq_messages.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 9315a4c5..91149aec 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -923,7 +923,6 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& } bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderGroup &msg) { - switch (msg.current_pos) { case 0: { if(!parse_uintV_field(buffer, msg.subscribe_id)) { @@ -946,7 +945,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderGroup msg.current_pos += 1; } break; - case 4: { + case 3: { if(!parse_uintV_field(buffer, msg.priority)) { return false; } diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index ec116dfd..917cca61 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -580,4 +580,77 @@ TEST_CASE("ObjectStream Message encode/decode") CHECK_EQ(object_stream.object_id, object_stream_out.object_id); CHECK_EQ(object_stream.priority, object_stream_out.priority); CHECK_EQ(object_stream.payload, object_stream_out.payload); +} + +TEST_CASE("ObjectDatagram Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto object_datagram = MoqObjectDatagram {}; + object_datagram.subscribe_id = 0x100; + object_datagram.track_alias = TRACK_ALIAS_ALICE_VIDEO; + object_datagram.group_id = 0x1000; + object_datagram.object_id = 0xFF; + object_datagram.priority =0xA; + object_datagram.payload = {0x1, 0x2, 0x3, 0x5, 0x6}; + + buffer << object_datagram; + + std::vector net_data = buffer.front(buffer.size()); + + MoqObjectStream object_datagram_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_OBJECT_DATAGRAM), object_datagram_out)); + CHECK_EQ(object_datagram.subscribe_id, object_datagram_out.subscribe_id); + CHECK_EQ(object_datagram.track_alias, object_datagram_out.track_alias); + CHECK_EQ(object_datagram.group_id, object_datagram_out.group_id); + CHECK_EQ(object_datagram.object_id, object_datagram_out.object_id); + CHECK_EQ(object_datagram.priority, object_datagram_out.priority); + CHECK_EQ(object_datagram.payload, object_datagram_out.payload); +} + +TEST_CASE("StreamPerGroup Object Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto hdr_grp = MoqStreamHeaderGroup {}; + hdr_grp.subscribe_id = 0x100; + hdr_grp.track_alias = TRACK_ALIAS_ALICE_VIDEO; + hdr_grp.group_id = 0x1000; + hdr_grp.priority = 0xA; + + buffer << hdr_grp; + + std::vector net_data = buffer.front(buffer.size()); + MoqStreamHeaderGroup hdr_group_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_STREAM_HEADER_GROUP), hdr_group_out)); + CHECK_EQ(hdr_grp.subscribe_id, hdr_group_out.subscribe_id); + CHECK_EQ(hdr_grp.track_alias, hdr_group_out.track_alias); + CHECK_EQ(hdr_grp.group_id, hdr_group_out.group_id); + + buffer.pop(buffer.size()); + auto objects = std::vector{}; + // send 10 objects + for(size_t i = 0; i < 10; i++) { + auto obj = MoqStreamGroupObject{}; + obj.object_id = i; + obj.payload = {0x1, 0x2, 0x3, 0x4, 0x5}; + objects.push_back(obj); + buffer << obj; + } + + net_data.clear(); + net_data = buffer.front(buffer.size()); + size_t object_count = 0; + for(const auto v: net_data) { + buffer.push(v); + bool done = false; + auto obj_out = MoqStreamGroupObject{}; + done = buffer >> obj_out; + if (done) { + CHECK_EQ(obj_out.object_id, objects[object_count].object_id); + CHECK_EQ(obj_out.payload, objects[object_count].payload); + // got one object + object_count++; + } + } + + CHECK_EQ(object_count, 10); } \ No newline at end of file From 7db2053745c9f86354912953fc73f2daeb2f633f Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Tue, 4 Jun 2024 16:26:01 -0700 Subject: [PATCH 21/71] instance and delegate updates --- include/quicr/moq_delegate_track_publish.h | 16 -- include/quicr/moq_delegate_track_subscribe.h | 18 -- include/quicr/moq_instance.h | 219 +++++++++++++++++- ...ate_instance.h => moq_instance_delegate.h} | 2 +- include/quicr/moq_track_delegate.h | 189 +++++++++++++++ 5 files changed, 405 insertions(+), 39 deletions(-) delete mode 100644 include/quicr/moq_delegate_track_publish.h delete mode 100644 include/quicr/moq_delegate_track_subscribe.h rename include/quicr/{moq_delegate_instance.h => moq_instance_delegate.h} (94%) create mode 100644 include/quicr/moq_track_delegate.h diff --git a/include/quicr/moq_delegate_track_publish.h b/include/quicr/moq_delegate_track_publish.h deleted file mode 100644 index b9e0759d..00000000 --- a/include/quicr/moq_delegate_track_publish.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2024 - * Cisco Systems, Inc. - * All Rights Reserved - */ - -#pragma once - -namespace quicr { - -class MOQTrackPublishDelegate -{ - -}; - -} // namespace quicr diff --git a/include/quicr/moq_delegate_track_subscribe.h b/include/quicr/moq_delegate_track_subscribe.h deleted file mode 100644 index 945df79c..00000000 --- a/include/quicr/moq_delegate_track_subscribe.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2024 - * Cisco Systems, Inc. - * All Rights Reserved - */ - -#pragma once - - -namespace quicr { - -class MOQTrackSubscribeDelegate -{ - -}; - - -} // namespace quicr diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index 94f281fb..b2174512 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -6,13 +6,224 @@ #pragma once -#include -#include -#include +#include +#include +#include + +#include +#include namespace quicr { +using namespace qtransport; + +class MoQInstanceConfig +{ + RelayInfo relay_info; + + // TODO(tievens): Convert to use URL instead of Relay Info + + std::string instance_id; /// Instance ID for the client or server, should be unique + TransportConfig transport_config; +}; + +class MoQInstance : public ITransport::TransportDelegate +{ + +public: + /** + * + * @param cfg MoQ Instance Configuration + * @param logger MoQ Log pointer to parent logger + */ + MoQInstance(const MoQInstanceConfig& cfg, + const cantina::LoggerPointer& logger); + + ~MoQInstance(); + + // ------------------------------------------------------------------------------------------------- + // Public API MoQ Intance API methods + // ------------------------------------------------------------------------------------------------- + /** + * @brief Subscribe to a track + * + * @param track_delegate Track delegate to use for track related functions and callbacks + * + * @returns `track_alias` if no error and nullopt on error + */ + std::optional subscribeTrack(TransportConnId conn_id, std::shared_ptr track_delegate); + + + /** + * @brief Publish to a track + * + * @param track_delegate Track delegate to use for track related functions + * and callbacks + * + * @returns `track_alias` if no error and nullopt on error + */ + std::optional publishTrack(TransportConnId conn_id, std::shared_ptr track_delegate); + + + // ------------------------------------------------------------------------------------------------- + + /* + * Transport Delegete/callback functions + */ + void on_new_data_context(const TransportConnId& conn_id, + const DataContextId& data_ctx_id) override + { + } + void on_connection_status(const TransportConnId& conn_id, + const TransportStatus status) override; + void on_new_connection(const TransportConnId& conn_id, + const TransportRemote& remote) override; + void on_recv_stream(const TransportConnId& conn_id, + uint64_t stream_id, + std::optional data_ctx_id, + const bool is_bidir = false) override; + void on_recv_dgram(const TransportConnId& conn_id, + std::optional data_ctx_id) override; + +private: + /** + * @brief Client thread to monitor client published messages + */ + void PeerQueueThread(); + + /** + * @brief Watch Thread to perform reconnects and cleanup + * @details Thread will perform various tasks each interval + * + * @param interval_ms Interval in milliseconds to sleep before running + * check + */ + void watchThread(int interval_ms); + + /** + * @brief Create a peering session/connection + * + * @param peer_config Peer/relay configuration parameters + * + */ + void createPeerSession(const TransportRemote& peer_config); + + /** + * @brief Send subscribe to first/best publish intent peers + * + * @details There is a single best (first in list) publish intent peer to send + * matching subscribes to for a given origin. This method will iterate over + * each matching publish intent best origin peer and send a subscribe. + * + * @param ns Namespace to subscribe + * @param source_peer_id Source peer that sent (or client manager) the + * intent + */ + void subscribePeers(const Namespace& ns, const peer_id_t& source_peer_id); + + /** + * @brief Send subscribe to specific peer + * + * @param ns Namespace to subscribe peers to + * @param peer_id Peer ID to send to + */ + void subscribePeer(const Namespace& ns, const peer_id_t& peer_id); + + /** + * @brief Send unsubscribe to peers that had previous subscribes for given + * namespace + * + * @param ns Namespace to unsubscribe peers to + * @param source_peer_id Source peer that sent (or client manager) the + * intent + */ + void unSubscribePeers(const Namespace& ns, const peer_id_t& source_peer_id); + + /** + * @brief Send unsubscribe to specific peer + * + * @param ns Namespace to unsubscribe peers to + * @param peer_id Peer ID to send to + */ + void unSubscribePeer(const Namespace& ns, const peer_id_t& peer_id); + + /** + * @brief Send publish intent to all peers + * + * @details Currently publish intents are flooded to all peers + * + * @param ns Namespace for publish intent + * @param source_peer_id Source peer that sent (or client manager) the + * intent + * @param origin_peer_id Origin peer/relay that has the publisher + * directly connected + */ + void publishIntentPeers(const Namespace& ns, + const peer_id_t& source_peer_id, + const peer_id_t& origin_peer_id); + + /** + * @brief Send publish intent done + * + * @brief Currently publish intents are flooded to all peers, so the done + * needs to be flooded as well + * + * @param ns Namespace for publish intent done + * @param source_peer_id Source peer that sent (or client manager) the + * intent + * @param origin_peer_id Origin peer/relay that has the publisher + * directly connected + */ + void publishIntentDonePeers(const Namespace& ns, + const peer_id_t& source_peer_id, + const peer_id_t& origin_peer_id); + + /** + * @brief Get the pointer to the session by peer_id + * + * @param peer_id Peer ID to get session + * + * @return Returns nullptr or peer session if found + */ + PeerSession* getPeerSession(const peer_id_t& peer_id); + + void addSubscribedPeer(const Namespace& ns, const peer_id_t& peer_id); + +private: + std::mutex _mutex; + std::atomic _stop{ false }; + const Config& _config; + + peerQueue& _peer_queue; + Cache& _cache; + ClientSubscriptions& _subscriptions; + + std::shared_ptr + _server_transport; /// Server Transport for inbound connections + + std::thread _client_rx_msg_thr; /// Client receive message thread + std::thread _watch_thr; /// Watch/task thread, handles reconnects + + /// Peer sessions that are accepted by the server + std::map _server_peer_sessions; + + std::vector + _client_peer_sessions; /// Peer sessions that are initiated by the peer + /// manager + + // TODO: Fix to use list for peer sessions subscribed + namespace_map> + _peer_sess_subscribe_sent; /// Peers that subscribes have been sent + namespace_map> + _peer_sess_subscribe_recv; /// Peers that subscribes have been received + namespace_map>> + _pub_intent_namespaces; /// Publish intents received from peers + + // Log handler to use + cantina::LoggerPointer logger; -class MOQInstance { +#ifndef LIBQUICR_WITHOUT_INFLUXDB + std::shared_ptr _mexport; +#endif }; } // namespace quicr diff --git a/include/quicr/moq_delegate_instance.h b/include/quicr/moq_instance_delegate.h similarity index 94% rename from include/quicr/moq_delegate_instance.h rename to include/quicr/moq_instance_delegate.h index 183bc24a..81bd45ba 100644 --- a/include/quicr/moq_delegate_instance.h +++ b/include/quicr/moq_instance_delegate.h @@ -16,7 +16,7 @@ namespace quicr { * is a base class implementation and interface for callbacks. */ -class MOQInstanceDelegate +class MoQInstanceDelegate { }; diff --git a/include/quicr/moq_track_delegate.h b/include/quicr/moq_track_delegate.h new file mode 100644 index 00000000..21aef3ef --- /dev/null +++ b/include/quicr/moq_track_delegate.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2024 + * Cisco Systems, Inc. + * All Rights Reserved + */ +#pragma once + +#include + +namespace quicr { + +/** + * @brief MOQ track delegate for subscribe and publish + * + * @details MOQ track delegate defines all track related callbacks and + * functions. Track delegate operates on a single track (namespace + name). + * It can be used for subscribe, publish, or both subscribe and publish. The + * only requirement is that the namespace and track alias be the same. + */ +class MoQTrackDelegate +{ +public: + enum class ReadError : uint8_t + { + OK = 0, + NOT_AUTHORIZED, + NOT_SUBSCRIBED, + NO_DATA, + }; + + enum class SendError : uint8_t + { + OK = 0, + NOT_AUTHORIZED, + NOT_ANNOUNCED, + NO_SUBSCRIBERS, + }; + + enum class TrackReadStatus : uint8_t + { + NOT_AUTHORIZED = 0, + NOT_SUBSCRIBED, + PENDING_SUBSCRIBE_RESPONSE, + SUBSCRIBE_NOT_AUTHORIZED + }; + + enum class TrackSendStatus : uint8_t + { + NOT_ANNOUNCED = 0, + PENDING_ANNOUNCE_RESPONSE, + ANNOUNCE_NOT_AUTHORIZED, + NO_SUBSCRIBERS, + }; + + + // -------------------------------------------------------------------------- + // Public API methods that normally should not be overridden + // -------------------------------------------------------------------------- + + /** + * @brief Track delegate constructor + */ + MoQTrackDelegate(const bytes& track_namespace, + const bytes& track_name, + uint8_t default_priority, + uint32_t default_ttl); + + /** + * @brief Send object to announced track + * + * @details Send object to announced track that was previously announced. + * This will have an error if the track wasn't announced yet. Status will + * indicate if there are no subscribers. In this case, the object will + * not be sent. + * + * @param[in] object Object to send to track + * + * @returns SendError status of the send + * + */ + SendError sendObject(const std::span& object); + SendError sendObject(const std::span& object, uint32_t ttl); + + /** + * @brief Read object from track + * + * @details Reads an object from the subscribed track + * + * @param[out] object Refence to object to be updated. Will be cleared. + * + * @returns ReadError status of the read + */ + ReadError readObject(std::vector& object); + + /** + * @brief Current track read status + * + * @details Obtains the current track read status/state + * + * @returns current TrackReadStatus + */ + TrackReadStatus statusRead(); + + /** + * @brief Current track send status + * + * @details Obtains the current track send status/state + * + * @returns current TrackSendStatus + */ + TrackReadStatus statusSend(); + + /** + * @brief set/update the default priority for published objects + */ + void setDefaultPriority(uint8_t); + + /** + * @brief set/update the default TTL expirty for published objects + */ + void setDefaultTTL(uint32_t); + + // -------------------------------------------------------------------------- + // Public Virtual API callback event methods to be overridden + // -------------------------------------------------------------------------- + + /** + * @brief Notificaiton that data is avaialble to be read. + * + * @details Event notification to inform the caller that data can be read. The caller + * should read all data if possible. + * + * @param objects_available Number of objects available to be read at time of notification + */ + virtual void callback_objectsAvailable(uint64_t objects_available) = 0; + virtual void callback_objectReceived(std::vector&& object) = 0; + + /** + * @brief Notification that data can be sent + * @details Notification that an announcement has been successful and there is at least one + * subscriber for the track. Data can now be succesfully sent. + */ + virtual void callback_sendReady() = 0; + + /** + * @brief Notification that data can not be sent + * @details Notification that data cannot be sent yet with a reason. This will + * be called as it transitions through send states. + * + * @param status Indicates the reason for why data cannot be sent [yet] + */ + virtual void callback_sendNotReady(TrackSendStatus status) = 0; + + /** + * @brief Notification that the send queue is congested + * @details Notification indicates that send queue is backlogged and sending more + * will likely cause more congestion. + * + * @param cleared Indicates if congestion has cleared + * @param objects_in_queue Number of objects still pending to be sent at time of notification + */ + virtual void callback_sendCongested(bool cleared, uint64_t objects_in_queue) = 0; + + /** + * @brief Notification to indicate reading is ready + * @details Notification that an announcement has been successful and but + * there are no subscribers, so data cannot be sent yet. + */ + virtual void callback_readReady() = 0; + + /** + * @brief Notification that read is not available + * + * @param status Indicates the reason for why data cannot be sent [yet] + */ + virtual void callback_readNotReady(TrackReadStatus status) = 0; + + + + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + +private: + const bytes _track_namespace; + const bytes _track_name; + std::optional _track_alias; +}; + +} // namespace quicr From 8c5a17c79e870f11f9b9aa6b21e4b2b8f2b079f6 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Tue, 4 Jun 2024 16:27:41 -0700 Subject: [PATCH 22/71] Update transport --- dependencies/transport | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/transport b/dependencies/transport index ebf29645..3edbbdab 160000 --- a/dependencies/transport +++ b/dependencies/transport @@ -1 +1 @@ -Subproject commit ebf29645ce4eadb057a09d6b641c765f487e400d +Subproject commit 3edbbdab718b30a6fdbe9a092caf4e1459bb92c6 From 169ab6beb4f3272a441fd7691f6f34c34074fc98 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 19:24:59 -0700 Subject: [PATCH 23/71] get objectstream, objectdatagram and object group tests working --- test/moq_messages.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 917cca61..f5416a23 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -625,6 +625,7 @@ TEST_CASE("StreamPerGroup Object Message encode/decode") CHECK_EQ(hdr_grp.track_alias, hdr_group_out.track_alias); CHECK_EQ(hdr_grp.group_id, hdr_group_out.group_id); + // stream all the objects buffer.pop(buffer.size()); auto objects = std::vector{}; // send 10 objects @@ -638,17 +639,20 @@ TEST_CASE("StreamPerGroup Object Message encode/decode") net_data.clear(); net_data = buffer.front(buffer.size()); + auto obj_out = MoqStreamGroupObject{}; size_t object_count = 0; - for(const auto v: net_data) { - buffer.push(v); - bool done = false; - auto obj_out = MoqStreamGroupObject{}; - done = buffer >> obj_out; + qtransport::StreamBuffer in_buffer; + for(size_t i =0; i < net_data.size(); i++) { + in_buffer.push(net_data.at(i)); + bool done; + done = in_buffer >> obj_out; if (done) { CHECK_EQ(obj_out.object_id, objects[object_count].object_id); CHECK_EQ(obj_out.payload, objects[object_count].payload); // got one object object_count++; + obj_out = MoqStreamGroupObject{}; + in_buffer.pop(in_buffer.size()); } } From c76f78e30f766fc384e9b6da48da03196ec2aa54 Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 23:22:06 -0700 Subject: [PATCH 24/71] add test for stream-per-track encode/decode --- src/CMakeLists.txt | 4 +-- src/moq_messages.cpp | 2 ++ test/moq_messages.cpp | 62 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71e0adb3..ea8c28f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,8 +9,8 @@ target_sources(quicr PRIVATE quicr_client_raw_session.cpp quicr_server.cpp quicr_server_raw_session.cpp - moq_instance.cpp - moq_session.cpp + #moq_instance.cpp + #moq_session.cpp ) if(NOT LIBQUICR_WITHOUT_INFLUXDB) diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 91149aec..c0f5308a 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -857,6 +857,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderTrack return false; } msg.current_pos += 1; + msg.parse_completed = true; } break; } @@ -898,6 +899,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject if(!val) { return false; } + msg.payload = std::move(val.value()); msg.parse_completed = true; } break; diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index f5416a23..da4562e8 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -629,7 +629,7 @@ TEST_CASE("StreamPerGroup Object Message encode/decode") buffer.pop(buffer.size()); auto objects = std::vector{}; // send 10 objects - for(size_t i = 0; i < 10; i++) { + for(size_t i = 0; i < 1000; i++) { auto obj = MoqStreamGroupObject{}; obj.object_id = i; obj.payload = {0x1, 0x2, 0x3, 0x4, 0x5}; @@ -656,5 +656,63 @@ TEST_CASE("StreamPerGroup Object Message encode/decode") } } - CHECK_EQ(object_count, 10); + CHECK_EQ(object_count, 1000); +} + +TEST_CASE("StreamPerTrack Object Message encode/decode") +{ + qtransport::StreamBuffer buffer; + auto hdr = MoqStreamHeaderTrack {}; + hdr.subscribe_id = 0x100; + hdr.track_alias = TRACK_ALIAS_ALICE_VIDEO; + hdr.priority = 0xA; + + buffer << hdr; + + std::vector net_data = buffer.front(buffer.size()); + MoqStreamHeaderTrack hdr_out; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_STREAM_HEADER_TRACK), hdr_out)); + CHECK_EQ(hdr_out.subscribe_id, hdr_out.subscribe_id); + CHECK_EQ(hdr_out.track_alias, hdr_out.track_alias); + CHECK_EQ(hdr_out.priority, hdr_out.priority); + + // stream all the objects + buffer.pop(buffer.size()); + auto objects = std::vector{}; + // send 10 objects + for(size_t i = 0; i < 1000; i++) { + auto obj = MoqStreamTrackObject{}; + if ( i % 10 == 0) { + obj.group_id = i; + obj.object_id = 0; + } else { + obj.object_id = i; + } + + obj.payload = {0x1, 0x2, 0x3, 0x4, 0x5}; + objects.push_back(obj); + buffer << obj; + } + + net_data.clear(); + net_data = buffer.front(buffer.size()); + auto obj_out = MoqStreamTrackObject{}; + size_t object_count = 0; + qtransport::StreamBuffer in_buffer; + for(size_t i =0; i < net_data.size(); i++) { + in_buffer.push(net_data.at(i)); + bool done; + done = in_buffer >> obj_out; + if (done) { + CHECK_EQ(obj_out.group_id, objects[object_count].group_id); + CHECK_EQ(obj_out.object_id, objects[object_count].object_id); + CHECK_EQ(obj_out.payload, objects[object_count].payload); + // got one object + object_count++; + obj_out = MoqStreamTrackObject{}; + in_buffer.pop(in_buffer.size()); + } + } + + CHECK_EQ(object_count, 1000); } \ No newline at end of file From 3e81f6c60e955131ba0d37ccd24d0dadfd13fd4e Mon Sep 17 00:00:00 2001 From: suhasHere Date: Tue, 4 Jun 2024 23:27:16 -0700 Subject: [PATCH 25/71] add goaway encode/decode/test, cleanup old Apis --- include/quicr/moq_messages.h | 27 +++------------------------ src/moq_messages.cpp | 18 ++++++++++++++++++ test/moq_messages.cpp | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 7337b056..eb32eeed 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -109,9 +109,6 @@ struct MoqServerSetup { }; -MessageBuffer& operator<<(MessageBuffer &buffer, const MoqParameter &msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter &msg); - // // Subscribe // @@ -247,6 +244,9 @@ struct MoqAnnounceCancel { // struct MoqGoaway { quicr::bytes new_session_uri; + friend bool operator>>(qtransport::StreamBuffer &buffer, MoqGoaway &msg); + friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqGoaway& msg); }; MessageBuffer& operator<<(MessageBuffer& buffer, const MoqGoaway& msg); @@ -336,25 +336,4 @@ struct MoqStreamGroupObject { bool parse_completed { false }; }; -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqObjectStream& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqObjectStream &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqObjectDatagram& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqObjectDatagram &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamHeaderTrack& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamHeaderTrack &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamTrackObject& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamTrackObject &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamHeaderGroup& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamHeaderGroup &msg); -MessageBuffer& operator<<(MessageBuffer& buffer, const MoqStreamGroupObject& msg); -MessageBuffer& operator>>(MessageBuffer &buffer, MoqStreamGroupObject &msg); - -MessageBuffer& operator<<(MessageBuffer& buffer, const std::vector& val); -MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); -MessageBuffer& operator>>(MessageBuffer& msg, std::vector& val); - -template -MessageBuffer& operator<<(MessageBuffer& buffer, const std::optional& val); -template -MessageBuffer& operator>>(MessageBuffer& msg, std::optional& val); } \ No newline at end of file diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index c0f5308a..6b4f06c2 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -667,6 +667,24 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceCancel &ms // Goaway // +qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, + const MoqGoaway& msg){ + buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_GOAWAY))); + buffer.push_lv(msg.new_session_uri); + return buffer; +} + + +bool operator>>(qtransport::StreamBuffer &buffer, MoqGoaway &msg) { + + const auto val = buffer.decode_bytes(); + if (!val) { + return false; + } + msg.new_session_uri = std::move(val.value()); + return true; +} + MessageBuffer & operator<<(MessageBuffer &buffer, const MoqGoaway &msg) { buffer << static_cast(MESSAGE_TYPE_GOAWAY); diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index da4562e8..783699d2 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -715,4 +715,19 @@ TEST_CASE("StreamPerTrack Object Message encode/decode") } CHECK_EQ(object_count, 1000); +} + + +TEST_CASE("MoqGoaway Message encode/decode") +{ + qtransport::StreamBuffer buffer; + + auto goaway = MoqGoaway {}; + goaway.new_session_uri = from_ascii("go.away.now.no.return"); + buffer << goaway; + + std::vector net_data = buffer.front(buffer.size()); + MoqGoaway goaway_out{}; + CHECK(verify(net_data, static_cast(MESSAGE_TYPE_GOAWAY), goaway_out)); + CHECK_EQ(from_ascii("go.away.now.no.return"), goaway_out.new_session_uri); } \ No newline at end of file From 006a81b12ac339f77bc76513999cddcbb4ae2e27 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Wed, 5 Jun 2024 16:59:52 -0700 Subject: [PATCH 26/71] Update api methods and remove unused code --- .clang-format | 5 + include/quicr/moq_instance.h | 362 +++++++++++------------- include/quicr/moq_instance_delegate.h | 20 +- include/quicr/moq_track_delegate.h | 380 ++++++++++++++------------ src/CMakeLists.txt | 4 +- src/moq_session.h | 1 + 6 files changed, 375 insertions(+), 397 deletions(-) diff --git a/.clang-format b/.clang-format index 2e8f10e1..72fa895f 100644 --- a/.clang-format +++ b/.clang-format @@ -3,5 +3,10 @@ Language: Cpp BasedOnStyle: Mozilla BreakAfterAttributes: Leave +ColumnLimit: 120 +IndentWidth: 4 +NamespaceIndentation: All +BraceWrapping: + AfterFunction: false ... diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index b2174512..f8ef3056 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -14,216 +14,162 @@ #include namespace quicr { -using namespace qtransport; - -class MoQInstanceConfig -{ - RelayInfo relay_info; - - // TODO(tievens): Convert to use URL instead of Relay Info - - std::string instance_id; /// Instance ID for the client or server, should be unique - TransportConfig transport_config; -}; - -class MoQInstance : public ITransport::TransportDelegate -{ - -public: - /** - * - * @param cfg MoQ Instance Configuration - * @param logger MoQ Log pointer to parent logger - */ - MoQInstance(const MoQInstanceConfig& cfg, - const cantina::LoggerPointer& logger); - - ~MoQInstance(); - - // ------------------------------------------------------------------------------------------------- - // Public API MoQ Intance API methods - // ------------------------------------------------------------------------------------------------- - /** - * @brief Subscribe to a track - * - * @param track_delegate Track delegate to use for track related functions and callbacks - * - * @returns `track_alias` if no error and nullopt on error - */ - std::optional subscribeTrack(TransportConnId conn_id, std::shared_ptr track_delegate); - - - /** - * @brief Publish to a track - * - * @param track_delegate Track delegate to use for track related functions - * and callbacks - * - * @returns `track_alias` if no error and nullopt on error - */ - std::optional publishTrack(TransportConnId conn_id, std::shared_ptr track_delegate); - - - // ------------------------------------------------------------------------------------------------- - - /* - * Transport Delegete/callback functions - */ - void on_new_data_context(const TransportConnId& conn_id, - const DataContextId& data_ctx_id) override - { - } - void on_connection_status(const TransportConnId& conn_id, - const TransportStatus status) override; - void on_new_connection(const TransportConnId& conn_id, - const TransportRemote& remote) override; - void on_recv_stream(const TransportConnId& conn_id, - uint64_t stream_id, - std::optional data_ctx_id, - const bool is_bidir = false) override; - void on_recv_dgram(const TransportConnId& conn_id, - std::optional data_ctx_id) override; - -private: - /** - * @brief Client thread to monitor client published messages - */ - void PeerQueueThread(); - - /** - * @brief Watch Thread to perform reconnects and cleanup - * @details Thread will perform various tasks each interval - * - * @param interval_ms Interval in milliseconds to sleep before running - * check - */ - void watchThread(int interval_ms); - - /** - * @brief Create a peering session/connection - * - * @param peer_config Peer/relay configuration parameters - * - */ - void createPeerSession(const TransportRemote& peer_config); - - /** - * @brief Send subscribe to first/best publish intent peers - * - * @details There is a single best (first in list) publish intent peer to send - * matching subscribes to for a given origin. This method will iterate over - * each matching publish intent best origin peer and send a subscribe. - * - * @param ns Namespace to subscribe - * @param source_peer_id Source peer that sent (or client manager) the - * intent - */ - void subscribePeers(const Namespace& ns, const peer_id_t& source_peer_id); - - /** - * @brief Send subscribe to specific peer - * - * @param ns Namespace to subscribe peers to - * @param peer_id Peer ID to send to - */ - void subscribePeer(const Namespace& ns, const peer_id_t& peer_id); - - /** - * @brief Send unsubscribe to peers that had previous subscribes for given - * namespace - * - * @param ns Namespace to unsubscribe peers to - * @param source_peer_id Source peer that sent (or client manager) the - * intent - */ - void unSubscribePeers(const Namespace& ns, const peer_id_t& source_peer_id); - - /** - * @brief Send unsubscribe to specific peer - * - * @param ns Namespace to unsubscribe peers to - * @param peer_id Peer ID to send to - */ - void unSubscribePeer(const Namespace& ns, const peer_id_t& peer_id); - - /** - * @brief Send publish intent to all peers - * - * @details Currently publish intents are flooded to all peers - * - * @param ns Namespace for publish intent - * @param source_peer_id Source peer that sent (or client manager) the - * intent - * @param origin_peer_id Origin peer/relay that has the publisher - * directly connected - */ - void publishIntentPeers(const Namespace& ns, - const peer_id_t& source_peer_id, - const peer_id_t& origin_peer_id); - - /** - * @brief Send publish intent done - * - * @brief Currently publish intents are flooded to all peers, so the done - * needs to be flooded as well - * - * @param ns Namespace for publish intent done - * @param source_peer_id Source peer that sent (or client manager) the - * intent - * @param origin_peer_id Origin peer/relay that has the publisher - * directly connected - */ - void publishIntentDonePeers(const Namespace& ns, - const peer_id_t& source_peer_id, - const peer_id_t& origin_peer_id); - - /** - * @brief Get the pointer to the session by peer_id - * - * @param peer_id Peer ID to get session - * - * @return Returns nullptr or peer session if found - */ - PeerSession* getPeerSession(const peer_id_t& peer_id); - - void addSubscribedPeer(const Namespace& ns, const peer_id_t& peer_id); - -private: - std::mutex _mutex; - std::atomic _stop{ false }; - const Config& _config; - - peerQueue& _peer_queue; - Cache& _cache; - ClientSubscriptions& _subscriptions; - - std::shared_ptr - _server_transport; /// Server Transport for inbound connections - - std::thread _client_rx_msg_thr; /// Client receive message thread - std::thread _watch_thr; /// Watch/task thread, handles reconnects - - /// Peer sessions that are accepted by the server - std::map _server_peer_sessions; - - std::vector - _client_peer_sessions; /// Peer sessions that are initiated by the peer - /// manager - - // TODO: Fix to use list for peer sessions subscribed - namespace_map> - _peer_sess_subscribe_sent; /// Peers that subscribes have been sent - namespace_map> - _peer_sess_subscribe_recv; /// Peers that subscribes have been received - namespace_map>> - _pub_intent_namespaces; /// Publish intents received from peers - - // Log handler to use - cantina::LoggerPointer logger; + using namespace qtransport; + + + + class MoQInstanceConfig + { + std::string instance_id; /// Instance ID for the client or server, should be unique + TransportConfig transport_config; + }; + + class MoQInstanceClientConfig : MoQInstanceConfig + { + std::string server_host_ip; /// Relay hostname or IP to connect to + uint16_t server__port; /// Relay port to connect to + TransportProtocol server_proto; /// Protocol to use when connecting to relay + }; + + class MoQInstanceServerConfig : MoQInstanceConfig + { + std::string server_bind_ip; /// IP address to bind to, can be 0.0.0.0 + uint16_t server_port; /// Listening port for server + TransportProtocol server_proto; /// Protocol to use + }; + + /** + * @brief MOQ Instance + * @Details MoQ Instance is the handler for either a client or server. It can run + * in only one mode, client or server. + */ + class MoQInstance + { + public: + enum class Status : uint8_t + { + READY = 0, + + ERROR_NOT_IN_CLIENT_MODE, + ERROR_NOT_IN_SERVER_MODE, + + CLIENT_INVALID_PARAMS, + CLIENT_NOT_CONNECTED, + CLIENT_CONNECTING, + }; + + /** + * @brief Client mode Constructor to create the MOQ instance + * + * @param cfg MoQ Instance Client Configuration + * @param logger MoQ Log pointer to parent logger + */ + MoQInstance(const MoQInstanceClientConfig& cfg, const cantina::LoggerPointer& logger); + + /** + * @brief Server mode Constructor to create the MOQ instance + * + * @param cfg MoQ Instance Server Configuration + * @param logger MoQ Log pointer to parent logger + */ + MoQInstance(const MoQInstanceServerConfig& cfg, const cantina::LoggerPointer& logger); + + ~MoQInstance(); + + // ------------------------------------------------------------------------------------------------- + // Public API MoQ Intance API methods + // ------------------------------------------------------------------------------------------------- + /** + * @brief Subscribe to a track + * + * @param track_delegate Track delegate to use for track related functions and callbacks + * + * @returns `track_alias` if no error and nullopt on error + */ + std::optional subscribeTrack(TransportConnId conn_id, + std::shared_ptr track_delegate); + + /** + * @brief Publish to a track + * + * @param track_delegate Track delegate to use for track related functions + * and callbacks + * + * @returns `track_alias` if no error and nullopt on error + */ + std::optional publishTrack(TransportConnId conn_id, + std::shared_ptr track_delegate); + + /** + * @brief Make client connection + * + * @details Makes a client connection session if instance is in client mode + * + * @return Status indicating state or error. If succesful, status will be + * CLIENT_CONNECTING. + */ + Status client_connect(); + + /** + * @brief Get the instance status + * + * @return Status indicating the state/status of the instance + */ + Status status(); + + private: + /** + * @brief Create a transport session/connection + * + * @param peer_config Peer/relay configuration parameters + * + */ + void createSession(const TransportRemote& peer_config); + + private: + std::mutex _mutex; + const bool _client_mode; + std::atomic _stop{ false }; + const MoQInstanceServerConfig& _server_config; + const MoQInstanceClientConfig& _client_config; + + + // Log handler to use + cantina::LoggerPointer logger; #ifndef LIBQUICR_WITHOUT_INFLUXDB - std::shared_ptr _mexport; + std::shared_ptr _mexport; #endif -}; + }; + + /** + * @brief MOQ Instance Transport Delegate + * @details MOQ implements the transport delegate, which is shared by all sessions + */ + class MoQInstanceTransportDelegate : public ITransport::TransportDelegate + { + public: + MoQInstanceTransportDelegate(const MoQInstance& moq_instance, const cantina::LoggerPointer& logger) : + _moq_instance(moq_instance) + , _logger(std::make_shared("MOQ_ITD", logger)) + {} + + // ------------------------------------------------------------------------------------------------- + // Transprot Delegate/callback functions + // ------------------------------------------------------------------------------------------------- + + void on_new_data_context(const TransportConnId& conn_id, const DataContextId& data_ctx_id) override {} + void on_connection_status(const TransportConnId& conn_id, const TransportStatus status) override; + void on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) override; + void on_recv_stream(const TransportConnId& conn_id, + uint64_t stream_id, + std::optional data_ctx_id, + const bool is_bidir = false) override; + void on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) override; + + private: + cantina::LoggerPointer _logger; + const MoQInstance& _moq_instance; + }; } // namespace quicr diff --git a/include/quicr/moq_instance_delegate.h b/include/quicr/moq_instance_delegate.h index 81bd45ba..f369bcac 100644 --- a/include/quicr/moq_instance_delegate.h +++ b/include/quicr/moq_instance_delegate.h @@ -8,17 +8,15 @@ namespace quicr { -/** - * @brief Delegate for all MOQ/MOQT callbacks and interaction - * - * @details A new MOQ instance is created with this delegate as a shared pointer. - * All interaction with the instance is handled via this delegate. This delegate - * is a base class implementation and interface for callbacks. - */ - -class MoQInstanceDelegate -{ + /** + * @brief Delegate for all MOQ/MOQT callbacks and interaction + * + * @details A new MOQ instance is created with this delegate as a shared pointer. + * All interaction with the instance is handled via this delegate. This delegate + * is a base class implementation and interface for callbacks. + */ -}; + class MoQInstanceDelegate + {}; } // namespace quicr diff --git a/include/quicr/moq_track_delegate.h b/include/quicr/moq_track_delegate.h index 21aef3ef..68bff007 100644 --- a/include/quicr/moq_track_delegate.h +++ b/include/quicr/moq_track_delegate.h @@ -5,185 +5,213 @@ */ #pragma once +#include #include namespace quicr { -/** - * @brief MOQ track delegate for subscribe and publish - * - * @details MOQ track delegate defines all track related callbacks and - * functions. Track delegate operates on a single track (namespace + name). - * It can be used for subscribe, publish, or both subscribe and publish. The - * only requirement is that the namespace and track alias be the same. - */ -class MoQTrackDelegate -{ -public: - enum class ReadError : uint8_t - { - OK = 0, - NOT_AUTHORIZED, - NOT_SUBSCRIBED, - NO_DATA, - }; - - enum class SendError : uint8_t - { - OK = 0, - NOT_AUTHORIZED, - NOT_ANNOUNCED, - NO_SUBSCRIBERS, - }; - - enum class TrackReadStatus : uint8_t - { - NOT_AUTHORIZED = 0, - NOT_SUBSCRIBED, - PENDING_SUBSCRIBE_RESPONSE, - SUBSCRIBE_NOT_AUTHORIZED - }; - - enum class TrackSendStatus : uint8_t - { - NOT_ANNOUNCED = 0, - PENDING_ANNOUNCE_RESPONSE, - ANNOUNCE_NOT_AUTHORIZED, - NO_SUBSCRIBERS, - }; - - - // -------------------------------------------------------------------------- - // Public API methods that normally should not be overridden - // -------------------------------------------------------------------------- - - /** - * @brief Track delegate constructor - */ - MoQTrackDelegate(const bytes& track_namespace, - const bytes& track_name, - uint8_t default_priority, - uint32_t default_ttl); - - /** - * @brief Send object to announced track - * - * @details Send object to announced track that was previously announced. - * This will have an error if the track wasn't announced yet. Status will - * indicate if there are no subscribers. In this case, the object will - * not be sent. - * - * @param[in] object Object to send to track - * - * @returns SendError status of the send - * - */ - SendError sendObject(const std::span& object); - SendError sendObject(const std::span& object, uint32_t ttl); - - /** - * @brief Read object from track - * - * @details Reads an object from the subscribed track - * - * @param[out] object Refence to object to be updated. Will be cleared. - * - * @returns ReadError status of the read - */ - ReadError readObject(std::vector& object); - - /** - * @brief Current track read status - * - * @details Obtains the current track read status/state - * - * @returns current TrackReadStatus - */ - TrackReadStatus statusRead(); - - /** - * @brief Current track send status - * - * @details Obtains the current track send status/state - * - * @returns current TrackSendStatus - */ - TrackReadStatus statusSend(); - - /** - * @brief set/update the default priority for published objects - */ - void setDefaultPriority(uint8_t); - - /** - * @brief set/update the default TTL expirty for published objects - */ - void setDefaultTTL(uint32_t); - - // -------------------------------------------------------------------------- - // Public Virtual API callback event methods to be overridden - // -------------------------------------------------------------------------- - - /** - * @brief Notificaiton that data is avaialble to be read. - * - * @details Event notification to inform the caller that data can be read. The caller - * should read all data if possible. - * - * @param objects_available Number of objects available to be read at time of notification - */ - virtual void callback_objectsAvailable(uint64_t objects_available) = 0; - virtual void callback_objectReceived(std::vector&& object) = 0; - - /** - * @brief Notification that data can be sent - * @details Notification that an announcement has been successful and there is at least one - * subscriber for the track. Data can now be succesfully sent. - */ - virtual void callback_sendReady() = 0; - - /** - * @brief Notification that data can not be sent - * @details Notification that data cannot be sent yet with a reason. This will - * be called as it transitions through send states. - * - * @param status Indicates the reason for why data cannot be sent [yet] - */ - virtual void callback_sendNotReady(TrackSendStatus status) = 0; - - /** - * @brief Notification that the send queue is congested - * @details Notification indicates that send queue is backlogged and sending more - * will likely cause more congestion. - * - * @param cleared Indicates if congestion has cleared - * @param objects_in_queue Number of objects still pending to be sent at time of notification - */ - virtual void callback_sendCongested(bool cleared, uint64_t objects_in_queue) = 0; - - /** - * @brief Notification to indicate reading is ready - * @details Notification that an announcement has been successful and but - * there are no subscribers, so data cannot be sent yet. - */ - virtual void callback_readReady() = 0; - - /** - * @brief Notification that read is not available - * - * @param status Indicates the reason for why data cannot be sent [yet] - */ - virtual void callback_readNotReady(TrackReadStatus status) = 0; - - - - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - -private: - const bytes _track_namespace; - const bytes _track_name; - std::optional _track_alias; -}; + /** + * @brief MOQ track delegate for subscribe and publish + * + * @details MOQ track delegate defines all track related callbacks and + * functions. Track delegate operates on a single track (namespace + name). + * It can be used for subscribe, publish, or both subscribe and publish. The + * only requirement is that the namespace and track alias be the same. + */ + class MoQTrackDelegate + { + public: + enum class ReadError : uint8_t + { + OK = 0, + NOT_AUTHORIZED, + NOT_SUBSCRIBED, + NO_DATA, + }; + + enum class SendError : uint8_t + { + OK = 0, + NOT_AUTHORIZED, + NOT_ANNOUNCED, + NO_SUBSCRIBERS, + }; + + enum class TrackReadStatus : uint8_t + { + NOT_AUTHORIZED = 0, + NOT_SUBSCRIBED, + PENDING_SUBSCRIBE_RESPONSE, + SUBSCRIBE_NOT_AUTHORIZED + }; + + enum class TrackSendStatus : uint8_t + { + NOT_ANNOUNCED = 0, + PENDING_ANNOUNCE_RESPONSE, + ANNOUNCE_NOT_AUTHORIZED, + NO_SUBSCRIBERS, + }; + + enum class TrackMode : uint8_t + { + DATAGRAM, + STREAM_PER_OBJECT, + STREAM_PER_GROUP, + STREAM_PER_TRACK + }; + + // -------------------------------------------------------------------------- + // Public API methods that normally should not be overridden + // -------------------------------------------------------------------------- + + /** + * @brief Track delegate constructor + */ + MoQTrackDelegate(const bytes& track_namespace, + const bytes& track_name, + const TrackMode track_mode, + uint8_t default_priority, + uint32_t default_ttl); + + /** + * @brief Send object to announced track + * + * @details Send object to announced track that was previously announced. + * This will have an error if the track wasn't announced yet. Status will + * indicate if there are no subscribers. In this case, the object will + * not be sent. + * + * @param[in] object Object to send to track + * + * @returns SendError status of the send + * + */ + SendError sendObject(const std::span& object); + SendError sendObject(const std::span& object, uint32_t ttl); + + /** + * @brief Read object from track + * + * @details Reads an object from the subscribed track + * + * @param[out] object Refence to object to be updated. Will be cleared. + * + * @returns ReadError status of the read + */ + ReadError readObject(std::vector& object); + + /** + * @brief Current track read status + * + * @details Obtains the current track read status/state + * + * @returns current TrackReadStatus + */ + TrackReadStatus statusRead(); + + /** + * @brief Current track send status + * + * @details Obtains the current track send status/state + * + * @returns current TrackSendStatus + */ + TrackReadStatus statusSend(); + + /** + * @brief set/update the default priority for published objects + */ + void setDefaultPriority(uint8_t); + + /** + * @brief set/update the default TTL expirty for published objects + */ + void setDefaultTTL(uint32_t); + + // -------------------------------------------------------------------------- + // Public Virtual API callback event methods to be overridden + // -------------------------------------------------------------------------- + + /** + * @brief Notificaiton that data is avaialble to be read. + * + * @details Event notification to inform the caller that data can be read. The caller + * should read all data if possible. + * + * @param objects_available Number of objects available to be read at time of notification + */ + virtual void cb_objectsAvailable(uint64_t objects_available) = 0; + virtual void cb_objectReceived(std::vector&& object) = 0; + + /** + * @brief Notification that data can be sent + * @details Notification that an announcement has been successful and there is at least one + * subscriber for the track. Data can now be succesfully sent. + */ + virtual void cb_sendReady() = 0; + + /** + * @brief Notification that data can not be sent + * @details Notification that data cannot be sent yet with a reason. This will + * be called as it transitions through send states. + * + * @param status Indicates the reason for why data cannot be sent [yet] + */ + virtual void cb_sendNotReady(TrackSendStatus status) = 0; + + /** + * @brief Notification that the send queue is congested + * @details Notification indicates that send queue is backlogged and sending more + * will likely cause more congestion. + * + * @param cleared Indicates if congestion has cleared + * @param objects_in_queue Number of objects still pending to be sent at time of notification + */ + virtual void cb_sendCongested(bool cleared, uint64_t objects_in_queue) = 0; + + /** + * @brief Notification to indicate reading is ready + * @details Notification that an announcement has been successful and but + * there are no subscribers, so data cannot be sent yet. + */ + virtual void cb_readReady() = 0; + + /** + * @brief Notification that read is not available + * + * @param status Indicates the reason for why data cannot be sent [yet] + */ + virtual void cb_readNotReady(TrackReadStatus status) = 0; + + // -------------------------------------------------------------------------- + // Internal API methods used by MOQ instance and peering session + // -------------------------------------------------------------------------- + + /** + * @brief Set the track alias + * @details MOQ Instance session will set the track alias when the track has + * been assigned. + * + * @param track_alias MOQT track alias for track namespace+name that + * is relative to the sesssion + */ + void setTrackAlias(uint64_t track_alias) { _track_alias = track_alias; } + + /** + * @brief Get the track alias + * @returns Track alias as an optional. Track alias may not be set yet. If not + * set, nullopt will be returned. + */ + std::optional getTrackAlias() { return _track_alias; } + + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + + private: + const bytes _track_namespace; + const bytes _track_name; + std::optional _track_alias; + }; } // namespace quicr diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea8c28f6..71e0adb3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,8 +9,8 @@ target_sources(quicr PRIVATE quicr_client_raw_session.cpp quicr_server.cpp quicr_server_raw_session.cpp - #moq_instance.cpp - #moq_session.cpp + moq_instance.cpp + moq_session.cpp ) if(NOT LIBQUICR_WITHOUT_INFLUXDB) diff --git a/src/moq_session.h b/src/moq_session.h index 45efad6a..e3132321 100644 --- a/src/moq_session.h +++ b/src/moq_session.h @@ -6,6 +6,7 @@ #pragma once +#include #include "moq_session.h" namespace quicr { From fec7d89bbee2da1426c8074accb6d601570df75c Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Thu, 6 Jun 2024 17:07:40 -0700 Subject: [PATCH 27/71] Initial instance implementation --- cmd/CMakeLists.txt | 1 + include/quicr/moq_instance.h | 134 +++++++++++++--------- include/quicr/moq_instance_delegate.h | 53 ++++++++- include/quicr/moq_track_delegate.h | 3 +- src/moq_instance.cpp | 159 ++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 62 deletions(-) diff --git a/cmd/CMakeLists.txt b/cmd/CMakeLists.txt index 56c72004..5d66703c 100644 --- a/cmd/CMakeLists.txt +++ b/cmd/CMakeLists.txt @@ -1,3 +1,4 @@ #add_subdirectory(qcurl) add_subdirectory(really) +add_subdirectory(moq-example) diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index f8ef3056..a012e411 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -6,9 +6,9 @@ #pragma once -#include #include #include +#include #include #include @@ -16,28 +16,63 @@ namespace quicr { using namespace qtransport; - - - class MoQInstanceConfig + struct MoQInstanceConfig { std::string instance_id; /// Instance ID for the client or server, should be unique TransportConfig transport_config; }; - class MoQInstanceClientConfig : MoQInstanceConfig + struct MoQInstanceClientConfig : MoQInstanceConfig { std::string server_host_ip; /// Relay hostname or IP to connect to - uint16_t server__port; /// Relay port to connect to + uint16_t server_port; /// Relay port to connect to TransportProtocol server_proto; /// Protocol to use when connecting to relay }; - class MoQInstanceServerConfig : MoQInstanceConfig + struct MoQInstanceServerConfig : MoQInstanceConfig { std::string server_bind_ip; /// IP address to bind to, can be 0.0.0.0 uint16_t server_port; /// Listening port for server TransportProtocol server_proto; /// Protocol to use }; + class MoQInstance; + /** + * @brief MOQ Instance Transport Delegate + * @details MOQ implements the transport delegate, which is shared by all sessions + */ + class MoQInstanceTransportDelegate : public ITransport::TransportDelegate + { + public: + MoQInstanceTransportDelegate(MoQInstance& moq_instance, const cantina::LoggerPointer& logger) + : _moq_instance(moq_instance) + , _logger(std::make_shared("MOQ_ITD", logger)) + { + } + + // ------------------------------------------------------------------------------------------------- + // Transprot Delegate/callback functions + // ------------------------------------------------------------------------------------------------- + + void on_new_data_context([[maybe_unused]] const TransportConnId& conn_id, + [[maybe_unused]] const DataContextId& data_ctx_id) override + { + } + + void on_connection_status(const TransportConnId& conn_id, const TransportStatus status) override; + void on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) override; + void on_recv_stream(const TransportConnId& conn_id, + uint64_t stream_id, + std::optional data_ctx_id, + const bool is_bidir = false) override; + void on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) override; + + private: + MoQInstance& _moq_instance; + cantina::LoggerPointer _logger; + }; + + /** * @brief MOQ Instance * @Details MoQ Instance is the handler for either a client or server. It can run @@ -49,6 +84,7 @@ namespace quicr { enum class Status : uint8_t { READY = 0, + NOT_READY, ERROR_NOT_IN_CLIENT_MODE, ERROR_NOT_IN_SERVER_MODE, @@ -56,23 +92,30 @@ namespace quicr { CLIENT_INVALID_PARAMS, CLIENT_NOT_CONNECTED, CLIENT_CONNECTING, + CLIENT_FAILED_TO_CONNECT, }; /** * @brief Client mode Constructor to create the MOQ instance * * @param cfg MoQ Instance Client Configuration + * @param delegate MoQ instance delegate of callbacks * @param logger MoQ Log pointer to parent logger */ - MoQInstance(const MoQInstanceClientConfig& cfg, const cantina::LoggerPointer& logger); + MoQInstance(const MoQInstanceClientConfig& cfg, + std::shared_ptr delegate, + const cantina::LoggerPointer& logger); /** * @brief Server mode Constructor to create the MOQ instance * - * @param cfg MoQ Instance Server Configuration - * @param logger MoQ Log pointer to parent logger + * @param cfg MoQ Instance Server Configuration + * @param delegate MoQ instance delegate of callbacks + * @param logger MoQ Log pointer to parent logger */ - MoQInstance(const MoQInstanceServerConfig& cfg, const cantina::LoggerPointer& logger); + MoQInstance(const MoQInstanceServerConfig& cfg, + std::shared_ptr delegate, + const cantina::LoggerPointer& logger); ~MoQInstance(); @@ -101,75 +144,56 @@ namespace quicr { std::shared_ptr track_delegate); /** - * @brief Make client connection + * @brief Make client connection and run * - * @details Makes a client connection session if instance is in client mode + * @details Makes a client connection session if instance is in client mode and runs as client * * @return Status indicating state or error. If succesful, status will be * CLIENT_CONNECTING. */ - Status client_connect(); + Status run_client(); /** - * @brief Get the instance status + * @brief Start Server Listening * - * @return Status indicating the state/status of the instance + * @details Creates transport and listens for new connections + * + * @return Status indicating state or error. If succesful, status will be + * READY. */ - Status status(); + Status run_server(); + - private: /** - * @brief Create a transport session/connection - * - * @param peer_config Peer/relay configuration parameters + * @brief Get the instance status * + * @return Status indicating the state/status of the instance */ - void createSession(const TransportRemote& peer_config); + Status status(); + + private: + void init(); private: std::mutex _mutex; const bool _client_mode; std::atomic _stop{ false }; - const MoQInstanceServerConfig& _server_config; - const MoQInstanceClientConfig& _client_config; - + const MoQInstanceServerConfig _server_config; + const MoQInstanceClientConfig _client_config; + std::shared_ptr _delegate; + MoQInstanceTransportDelegate _transport_delegate; // Log handler to use - cantina::LoggerPointer logger; + cantina::LoggerPointer _logger; #ifndef LIBQUICR_WITHOUT_INFLUXDB - std::shared_ptr _mexport; + MetricsExporter _mexport; #endif - }; - /** - * @brief MOQ Instance Transport Delegate - * @details MOQ implements the transport delegate, which is shared by all sessions - */ - class MoQInstanceTransportDelegate : public ITransport::TransportDelegate - { - public: - MoQInstanceTransportDelegate(const MoQInstance& moq_instance, const cantina::LoggerPointer& logger) : - _moq_instance(moq_instance) - , _logger(std::make_shared("MOQ_ITD", logger)) - {} + std::shared_ptr _transport; - // ------------------------------------------------------------------------------------------------- - // Transprot Delegate/callback functions - // ------------------------------------------------------------------------------------------------- - - void on_new_data_context(const TransportConnId& conn_id, const DataContextId& data_ctx_id) override {} - void on_connection_status(const TransportConnId& conn_id, const TransportStatus status) override; - void on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) override; - void on_recv_stream(const TransportConnId& conn_id, - uint64_t stream_id, - std::optional data_ctx_id, - const bool is_bidir = false) override; - void on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) override; - - private: - cantina::LoggerPointer _logger; - const MoQInstance& _moq_instance; + std::vector _connections; }; + } // namespace quicr diff --git a/include/quicr/moq_instance_delegate.h b/include/quicr/moq_instance_delegate.h index f369bcac..5a4e2e3c 100644 --- a/include/quicr/moq_instance_delegate.h +++ b/include/quicr/moq_instance_delegate.h @@ -6,17 +6,58 @@ #pragma once +#include + namespace quicr { /** - * @brief Delegate for all MOQ/MOQT callbacks and interaction + * @brief MOQ/MOQT callbacks * - * @details A new MOQ instance is created with this delegate as a shared pointer. - * All interaction with the instance is handled via this delegate. This delegate - * is a base class implementation and interface for callbacks. + * @details MOQ instance is created and this delegate is passed to the instanct constructor. + * MOQ instance callbacks are defined in this delegate */ - class MoQInstanceDelegate - {}; + { + /** + * @brief Notification on new connection + * @details Notification that a new connection has been accepted + * + * @param conn_id Transport connection ID + * @param endpoint_id Endpoint ID of client connection + * @param remote Transport remote connection information + */ + virtual void cb_newConnection(TransportConnId conn_id, + const std::span& endpoint_id, + const TransportRemote& remote) = 0; + + /** + * @brief Notification for connection status/state change + * @details Notification indicates state change of connection, such as disconnected + * + * @param conn_id Transport connection ID + * @param status Transport status of connection id + */ + virtual void cb_connectionStatus(TransportConnId conn_id, + TransportStatus status) = 0; + + /** + * @brief callback on client setup message + * @detais In server mode, client will send a setup message on new connection. + * Server responds with server setup. + * + * @param conn_id Transport connection ID + * @param client_setup Decoded client setup message + */ + virtual void cb_clientSetup(TransportConnId conn_id, messages::MoqClientSetup client_setup) = 0; + + /** + * @brief callback on server setup message + * @details In client mode, server will send sever setup in response to client setup message sent. + * + * @param conn_id Transport connection ID + * @param server_setup Decoded sever setup message + */ + virtual void cb_serverSetup(TransportConnId conn_id, messages::MoqServerSetup server_setup) = 0; + }; } // namespace quicr diff --git a/include/quicr/moq_track_delegate.h b/include/quicr/moq_track_delegate.h index 68bff007..5b8f2538 100644 --- a/include/quicr/moq_track_delegate.h +++ b/include/quicr/moq_track_delegate.h @@ -70,7 +70,7 @@ namespace quicr { */ MoQTrackDelegate(const bytes& track_namespace, const bytes& track_name, - const TrackMode track_mode, + TrackMode track_mode, uint8_t default_priority, uint32_t default_ttl); @@ -211,6 +211,7 @@ namespace quicr { private: const bytes _track_namespace; const bytes _track_name; + TrackMode _track_mode; std::optional _track_alias; }; diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp index e36196c4..2055fd5f 100644 --- a/src/moq_instance.cpp +++ b/src/moq_instance.cpp @@ -8,4 +8,163 @@ namespace quicr { + MoQInstance::MoQInstance(const MoQInstanceClientConfig& cfg, + std::shared_ptr delegate, + const cantina::LoggerPointer& logger) : + _client_mode(true) + , _server_config({}) + , _client_config(cfg) + , _delegate(std::move(delegate)) + , _transport_delegate({*this, _logger}) + , _logger(std::make_shared("MOQ_IC", logger)) +#ifndef LIBQUICR_WITHOUT_INFLUXDB + , _mexport(logger) +#endif + , _transport({}) + { + _logger->info << "Created MoQ instance in client mode connecting to " + << cfg.server_host_ip << ":" << cfg.server_port + << std::flush; + + init(); + } + + MoQInstance::MoQInstance(const MoQInstanceServerConfig& cfg, + std::shared_ptr delegate, + const cantina::LoggerPointer& logger) + : _client_mode(false) + , _server_config(cfg) + , _client_config({}) + , _delegate(std::move(delegate)) + , _transport_delegate({*this, _logger}) + , _logger(std::make_shared("MOQ_IS", logger)) +#ifndef LIBQUICR_WITHOUT_INFLUXDB + , _mexport(logger) +#endif + , _transport({}) + { + _logger->info << "Created MoQ instance in server mode listening on " + << cfg.server_bind_ip << ":" << cfg.server_port + << std::flush; + + init(); + } + + void MoQInstance::init() + { + _logger->info << "Starting metrics exporter" << std::flush; + +#ifndef LIBQUICR_WITHOUT_INFLUXDB + if (_mexport.init("http://metrics.m10x.ctgpoc.com:8086", + "Media10x", + "cisco-cto-media10x") != + MetricsExporter::MetricsExporterError::NoError) { + throw std::runtime_error("Failed to connect to InfluxDB"); + } + + _mexport.run(); +#endif + } + + MoQInstance::Status MoQInstance::run_server() + { + TransportRemote server { .host_or_ip = _server_config.server_bind_ip, + .port = _server_config.server_port, + .proto = _server_config.server_proto }; + + _transport = ITransport::make_server_transport(server, _server_config.transport_config, + _transport_delegate, _logger); + +#ifndef LIBQUICR_WITHOUT_INFLUXDB + _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); +#else + _transport->start(nullptr, nullptr); +#endif + + switch (_transport->status()) { + case TransportStatus::Ready: + return Status::READY; + default: + return Status::NOT_READY; + } + } + + MoQInstance::Status MoQInstance::run_client() + { + TransportRemote relay { .host_or_ip = _client_config.server_host_ip, + .port = _client_config.server_port, + .proto = _client_config.server_proto }; + + _transport = ITransport::make_client_transport(relay, _client_config.transport_config, + _transport_delegate, _logger); + +#ifndef LIBQUICR_WITHOUT_INFLUXDB + _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); +#else + _transport->start(nullptr, nullptr); +#endif + + _connections.push_back(_transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples)); + + LOGGER_INFO(_logger, "Connecting session conn_id: " << _connections.front() << "..."); + + while (!_stop && _transport->status() == TransportStatus::Connecting) { + LOGGER_DEBUG(_logger, "Connecting... " << int(_stop.load())); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + if (_stop || !_transport) { + LOGGER_INFO(_logger, "Cancelling connecting session " << _connections.front()); + return Status::CLIENT_FAILED_TO_CONNECT; + } + + if (_transport->status() != TransportStatus::Ready) { + _logger->error << "Failed to connect status: " << static_cast(_transport->status()) << std::flush; + + return Status::CLIENT_FAILED_TO_CONNECT; + } + + return Status::READY; + } + + + void MoQInstanceTransportDelegate::on_connection_status(const TransportConnId& conn_id, const TransportStatus status) + { + _logger->info << "Connection status conn_id: " << conn_id + << " status: " << static_cast(status) + << std::flush; + + _moq_instance.status(); + } + + void MoQInstanceTransportDelegate::on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) + { + _logger->info << "New connection conn_id: " << conn_id + << " remote ip: " << remote.host_or_ip + << " port: " << remote.port + << std::flush; + + } + + void MoQInstanceTransportDelegate::on_recv_stream(const TransportConnId& conn_id, + uint64_t stream_id, + std::optional data_ctx_id, + const bool is_bidir) + { + _logger->info << "stream data conn_id: " << conn_id + << " stream_id: " << stream_id + << " data_ctx_id: " << (data_ctx_id ? *data_ctx_id : 0) + << " is_bidir: " << is_bidir + << std::flush; + } + + void MoQInstanceTransportDelegate::on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) + { + _logger->info << "datagram data conn_id: " << conn_id + << " data_ctx_id: " << (data_ctx_id ? *data_ctx_id : 0) + << std::flush; + + } + + } // namespace quicr From 4bb9a687854531ba9272ceb52ffa0cb0a026f6be Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Thu, 6 Jun 2024 21:14:11 -0700 Subject: [PATCH 28/71] moq-example --- cmd/moq-example/CMakeLists.txt | 35 +++++ cmd/moq-example/client.cpp | 252 +++++++++++++++++++++++++++++++ cmd/moq-example/server.cpp | 178 ++++++++++++++++++++++ cmd/moq-example/subscription.cpp | 63 ++++++++ cmd/moq-example/subscription.h | 44 ++++++ 5 files changed, 572 insertions(+) create mode 100644 cmd/moq-example/CMakeLists.txt create mode 100644 cmd/moq-example/client.cpp create mode 100644 cmd/moq-example/server.cpp create mode 100644 cmd/moq-example/subscription.cpp create mode 100644 cmd/moq-example/subscription.h diff --git a/cmd/moq-example/CMakeLists.txt b/cmd/moq-example/CMakeLists.txt new file mode 100644 index 00000000..d80a6112 --- /dev/null +++ b/cmd/moq-example/CMakeLists.txt @@ -0,0 +1,35 @@ +add_executable(qserver + server.cpp + subscription.cpp) +target_link_libraries(qserver PRIVATE quicr) + +target_compile_options(qserver + PRIVATE + $<$,$,$>: -Wpedantic -Wextra -Wall> + $<$: >) + +set_target_properties(qserver + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS ON) + +if(CLANG_TIDY_EXE) + set_target_properties(quicr PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE}") +endif() + + +add_executable(qclient client.cpp ) + +target_link_libraries(qclient LINK_PUBLIC quicr) + +target_compile_options(qclient + PRIVATE + $<$,$,$>: -Wpedantic -Wextra -Wall> + $<$: >) + +set_target_properties(qclient + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS OFF) diff --git a/cmd/moq-example/client.cpp b/cmd/moq-example/client.cpp new file mode 100644 index 00000000..1742d3f3 --- /dev/null +++ b/cmd/moq-example/client.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +class subDelegate : public quicr::SubscriberDelegate +{ +public: + explicit subDelegate(cantina::LoggerPointer& logger) + : logger(std::make_shared("SDEL", logger)) + { + } + + void onSubscribeResponse( + [[maybe_unused]] const quicr::Namespace& quicr_namespace, + [[maybe_unused]] const quicr::SubscribeResult& result) override + { + logger->info << "onSubscriptionResponse: name: " << quicr_namespace << "/" + << int(quicr_namespace.length()) + << " status: " << static_cast(result.status) + << std::flush; + } + + void onSubscriptionEnded( + [[maybe_unused]] const quicr::Namespace& quicr_namespace, + [[maybe_unused]] const quicr::SubscribeResult::SubscribeStatus& reason) + override + { + logger->info << "onSubscriptionEnded: name: " << quicr_namespace << "/" + << static_cast(quicr_namespace.length()) + << std::flush; + } + + void onSubscribedObject([[maybe_unused]] const quicr::Name& quicr_name, + [[maybe_unused]] uint8_t priority, + [[maybe_unused]] quicr::bytes&& data) override + { + logger->info << "recv object: name: " << quicr_name + << " data sz: " << data.size(); + + if (!data.empty()) { + logger->info << " data: " << data.data(); + } + + logger->info << std::flush; + } + + void onSubscribedObjectFragment( + [[maybe_unused]] const quicr::Name& quicr_name, + [[maybe_unused]] uint8_t priority, + [[maybe_unused]] const uint64_t& offset, + [[maybe_unused]] bool is_last_fragment, + [[maybe_unused]] quicr::bytes&& data) override + { + } + +private: + cantina::LoggerPointer logger; +}; + +class pubDelegate : public quicr::PublisherDelegate +{ +public: + std::atomic got_intent_response { false }; + + explicit pubDelegate(cantina::LoggerPointer& logger) + : logger(std::make_shared("PDEL", logger)) + { + } + + void onPublishIntentResponse( + const quicr::Namespace& quicr_namespace, + const quicr::PublishIntentResult& result) override + { + LOGGER_INFO(logger, + "Received PublishIntentResponse for " + << quicr_namespace << ": " + << static_cast(result.status)); + got_intent_response = true; + } + +private: + cantina::LoggerPointer logger; +}; + +int do_publisher(cantina::LoggerPointer logger, + quicr::Client& client, + std::shared_ptr pd, + quicr::Name name) { + + auto nspace = quicr::Namespace(name, 96); + logger->info << "Publish Intent for name: " << name + << " == namespace: " << nspace << std::flush; + + client.publishIntent(pd, nspace, {}, {}, {}, quicr::TransportMode::ReliablePerGroup, 2); + logger->info << "Waiting for intent response, up to 2.5 seconds" << std::flush; + + for (int c=0; c < 50; c++) { + if (pd->got_intent_response) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds (50)); + } + + if (!pd->got_intent_response) { + logger->info << "Did not receive publish intent, cannot proceed. Exit" << std::flush; + return -1; + } + + logger->info << "Received intent response." << std::flush; + + std::cout << "-----------------------------------------------------------------------" << std::endl; + std::cout << " Type a message and press ENTER to publish. Type the word exit to end program." << std::flush; + std::cout << "-----------------------------------------------------------------------" << std::endl; + + while (true) { + std::string msg; + getline(std::cin, msg); + if (!msg.compare("exit")) { + logger->info << "Exit" << std::flush; + break; + } + + logger->info << "Publish: " << msg << std::flush; + std::vector m_data(msg.begin(), msg.end()); + + std::vector trace; + const auto start_time = std::chrono::time_point_cast(std::chrono::steady_clock::now()); + + trace.push_back({"client:publish", start_time}); + + client.publishNamedObject(name++, 0, 1000, std::move(m_data), std::move(trace)); + } + + return 0; +} + +int do_subscribe(cantina::LoggerPointer logger, + quicr::Client& client, + quicr::Name name) { + + auto sd = std::make_shared(logger); + auto nspace = quicr::Namespace(name, 96); + + logger->info << "Subscribe to " << name << "/" << 96 << std::flush; + + client.subscribe(sd, + nspace, + quicr::SubscribeIntent::immediate, + quicr::TransportMode::ReliablePerGroup, + "origin_url", + "auth_token", + quicr::bytes{}); + + logger->info << "Type exit to end program" << std::flush; + while (true) { + std::string msg; + getline(std::cin, msg); + if (!msg.compare("exit")) { + logger->info << "Exit" << std::flush; + break; + } + } + + + logger->Log("Now unsubscribing"); + client.unsubscribe(nspace, {}, {}); + + logger->Log("Sleeping for 5 seconds before exiting"); + std::this_thread::sleep_for(std::chrono::seconds(5)); + + return 0; +} + +int +main(int argc, char* argv[]) +{ + cantina::LoggerPointer logger = + std::make_shared("reallyTest"); + + if ((argc != 2) && (argc != 3)) { + std::cerr + << "Relay address and port set in REALLY_RELAY and REALLY_PORT env " + "variables." + << std::endl; + std::cerr << std::endl; + std::cerr << "Usage PUB: reallyTest FF0001 pub" << std::endl; + std::cerr << "Usage SUB: reallyTest FF0000" << std::endl; + exit(-1); // NOLINT(concurrency-mt-unsafe) + } + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const auto* relayName = getenv("REALLY_RELAY"); + if (relayName == nullptr) { + relayName = "127.0.0.1"; + } + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const auto* portVar = getenv("REALLY_PORT"); + int port = 1234; + if (portVar != nullptr) { + port = atoi(portVar); // NOLINT(cert-err34-c) + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto name = quicr::Name(std::string(argv[1])); + + logger->info << "Name = " << name << std::flush; + + auto data = std::vector{}; + if (argc == 3) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + const auto data_str = std::string(argv[2]); + data.insert(data.end(), data_str.begin(), data_str.end()); + } + + logger->info << "Connecting to " << relayName << ":" << port << std::flush; + + const auto relay = + quicr::RelayInfo{ .hostname = relayName, + .port = uint16_t(port), + .proto = quicr::RelayInfo::Protocol::QUIC }; + + const auto tcfg = qtransport::TransportConfig{ + .tls_cert_filename = nullptr, + .tls_key_filename = nullptr, + }; + + quicr::Client client(relay, "a@cisco.com", 0, tcfg, logger); + auto pd = std::make_shared(logger); + + if (!client.connect()) { + logger->Log(cantina::LogLevel::Critical, "Transport connect failed"); + return 0; + } + + if (!data.empty()) { + if (do_publisher(logger, client, pd, name)) + return -1; + + } else { + if (do_subscribe(logger, client, name)) + return -1; + } + + return 0; +} diff --git a/cmd/moq-example/server.cpp b/cmd/moq-example/server.cpp new file mode 100644 index 00000000..de2cd7f8 --- /dev/null +++ b/cmd/moq-example/server.cpp @@ -0,0 +1,178 @@ + +#include + +#include +#include +#include + +#include "subscription.h" + + +// Module-level variables used to control program execution +namespace moq_server { + static std::mutex main_mutex; // Main's mutex + static bool terminate{ false }; // Termination flag + static std::condition_variable cv; // Main thread waits on this + static const char* termination_reason{ nullptr }; // Termination reason +} + +/* + * signalHandler + * + * Description: + * This function will handle operating system signals related to + * termination and then instruct the main thread to terminate. + * + * Parameters: + * signal_number [in] + * The signal caught. + * + * Returns: + * Nothing. + * + * Comments: + * None. + */ +void +signalHandler(int signal_number) +{ + const auto lock = std::lock_guard(moq_server::main_mutex); + + // If termination is in process, just return + if (moq_server::terminate) { + return; + } + + // Indicate that the process should terminate + moq_server::terminate = true; + + // Set the termination reason string + switch (signal_number) { + case SIGINT: + moq_server::termination_reason = "Interrupt signal received"; + break; + +#ifndef _WIN32 + case SIGHUP: + moq_server::termination_reason = "Hangup signal received"; + break; + + case SIGQUIT: + moq_server::termination_reason = "Quit signal received"; + break; +#endif + + default: + moq_server::termination_reason = "Unknown signal received"; + break; + } + + // Notify the main execution thread to terminate + moq_server::cv.notify_one(); +} + +/* + * installSignalHandlers + * + * Description: + * This function will install the signal handlers for SIGINT, SIGQUIT, + * etc. so that the process can be terminated in a controlled fashion. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * None. + */ +void +installSignalHandlers() +{ +#ifdef _WIN32 + if (signal(SIGINT, signalHandler) == SIG_ERR) { + std::cerr << "Failed to install SIGINT handler" << std::endl; + } +#else + struct sigaction sa = {}; + + // Configure the sigaction struct + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + // Catch SIGHUP (signal 1) + if (sigaction(SIGHUP, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGHUP handler" << std::endl; + } + + // Catch SIGINT (signal 2) + if (sigaction(SIGINT, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGINT handler" << std::endl; + } + + // Catch SIGQUIT (signal 3) + if (sigaction(SIGQUIT, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGQUIT handler" << std::endl; + } +#endif +} + +class serverDelegate : public quicr::MoQInstanceDelegate +{ + void cb_newConnection(qtransport::TransportConnId conn_id, + const std::span& endpoint_id, + const qtransport::TransportRemote& remote) override {} + void cb_connectionStatus(qtransport::TransportConnId conn_id, qtransport::TransportStatus status) override {} + void cb_clientSetup(qtransport::TransportConnId conn_id, quicr::messages::MoqClientSetup client_setup) override {} + void cb_serverSetup(qtransport::TransportConnId conn_id, quicr::messages::MoqServerSetup server_setup) override {} +}; + +int +main() +{ + int result_code = EXIT_SUCCESS; + + // Install a signal handlers to catch operating system signals + installSignalHandlers(); + + // Lock the mutex so that main can then wait on it + std::unique_lock lock(moq_server::main_mutex); + + quicr::MoQInstanceServerConfig config; + config.instance_id = "moq_server"; + config.server_bind_ip = "127.0.0.1"; + config.server_port = 1234; + config.server_proto = qtransport::TransportProtocol::QUIC; + config.transport_config.debug = true; + config.transport_config.tls_cert_filename = "./server-cert.pem"; + config.transport_config.tls_key_filename = "./server-key.pem"; + + auto logger = std::make_shared("moq_server"); + + auto delegate = std::make_shared(); + + try { + auto moqInstance = quicr::MoQInstance{config, delegate, logger}; + + moqInstance.run_server(); + + // Wait until told to terminate + moq_server::cv.wait(lock, [&]() { return moq_server::terminate; }); + + // Unlock the mutex + lock.unlock(); + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid argument: " << e.what() << std::endl; + result_code = EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << "Unexpected exception: " << e.what() << std::endl; + result_code = EXIT_FAILURE; + } catch (...) { + std::cerr << "Unexpected exception" << std::endl; + result_code = EXIT_FAILURE; + } + + return result_code; +} diff --git a/cmd/moq-example/subscription.cpp b/cmd/moq-example/subscription.cpp new file mode 100644 index 00000000..80b782ef --- /dev/null +++ b/cmd/moq-example/subscription.cpp @@ -0,0 +1,63 @@ +#include "subscription.h" + +Subscriptions::Subscriptions() +{ + subscriptions.resize(129 /* 128 + 1 to handle zero and 128 */); +} + +void +Subscriptions::add(const quicr::Name& name, uint8_t len, const Remote& remote) +{ + + const auto prefix = quicr::Namespace{ name, len }; + + auto mapPtr = subscriptions[len].find(prefix.name()); + if (mapPtr == subscriptions[len].end()) { + std::set list; + list.insert(remote); + std::pair> pair; + pair = make_pair(prefix.name(), list); + subscriptions[len].insert(pair); + } else { + auto& list = mapPtr->second; + if (list.find(remote) == list.end()) { + list.insert( + remote); // TODO(trigaux) - rethink if list is right thing here + } + } +} + +void +Subscriptions::remove(const quicr::Name& name, + uint8_t len, + const Remote& remote) +{ + const auto prefix = quicr::Namespace{ name, len }; + + auto mapPtr = subscriptions[len].find(prefix.name()); + if (mapPtr != subscriptions[len].end()) { + auto& list = mapPtr->second; + if (list.find(remote) == list.end()) { + list.erase(remote); + } + } +} + +std::list +Subscriptions::find(const quicr::Name& name) +{ + std::list all_remotes; + + // TODO(trigaux): Fix this to not have to iterate for each mask bit + for (uint8_t len = 0; len <= 128; len++) { + const auto prefix = quicr::Namespace{ name, len }; + + auto mapPtr = subscriptions[len].find(prefix.name()); + if (mapPtr != subscriptions[len].end()) { + const auto& list = mapPtr->second; + all_remotes.insert(all_remotes.end(), list.begin(), list.end()); + } + } + + return all_remotes; +} diff --git a/cmd/moq-example/subscription.h b/cmd/moq-example/subscription.h new file mode 100644 index 00000000..a31e23a4 --- /dev/null +++ b/cmd/moq-example/subscription.h @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include +#include + +class Subscriptions +{ +public: + struct Remote + { + uint64_t subscribe_id {0}; + uint64_t conn_id {0}; /// Connection ID is used only for detecting if subscription is to the same connection + + bool operator==(const Remote& o) const + { + return subscribe_id == o.subscribe_id; + } + + bool operator<(const Remote& o) const + { + return std::tie(subscribe_id) < + std::tie(o.subscribe_id); + } + }; + + Subscriptions(); + + void get_masked_quicRName(const quicr::Name& src, + quicr::Name& dst, + const int len); + + void add(const quicr::Name& name, uint8_t len, const Remote& remote); + + void remove(const quicr::Name& name, uint8_t len, const Remote& remote); + + std::list find(const quicr::Name& name); + +private: + std::vector>> subscriptions; +}; From d5a08d13a31cd1d0920b9887177396d7212ba4b2 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Fri, 7 Jun 2024 16:51:58 -0700 Subject: [PATCH 29/71] Initial moq-example and instance implementation --- cmd/CMakeLists.txt | 1 - cmd/moq-example/CMakeLists.txt | 17 +- cmd/moq-example/client.cpp | 288 ++++++-------------------- cmd/moq-example/server.cpp | 123 +---------- cmd/moq-example/subscription.h | 2 + include/quicr/moq_instance.h | 100 ++++----- include/quicr/moq_instance_delegate.h | 2 + include/quicr/moq_messages.h | 47 +++-- src/moq_instance.cpp | 128 ++++++++---- src/moq_messages.cpp | 41 ++-- test/moq_messages.cpp | 50 ++--- 11 files changed, 304 insertions(+), 495 deletions(-) diff --git a/cmd/CMakeLists.txt b/cmd/CMakeLists.txt index 5d66703c..d3d3a2b5 100644 --- a/cmd/CMakeLists.txt +++ b/cmd/CMakeLists.txt @@ -1,4 +1,3 @@ #add_subdirectory(qcurl) add_subdirectory(really) add_subdirectory(moq-example) - diff --git a/cmd/moq-example/CMakeLists.txt b/cmd/moq-example/CMakeLists.txt index d80a6112..7e3457b6 100644 --- a/cmd/moq-example/CMakeLists.txt +++ b/cmd/moq-example/CMakeLists.txt @@ -1,8 +1,15 @@ + add_executable(qserver server.cpp subscription.cpp) target_link_libraries(qserver PRIVATE quicr) +if(NOT LIBQUICR_WITHOUT_INFLUXDB) + target_link_libraries(qserver + PRIVATE InfluxData::InfluxDB) +endif() + + target_compile_options(qserver PRIVATE $<$,$,$>: -Wpedantic -Wextra -Wall> @@ -19,15 +26,21 @@ if(CLANG_TIDY_EXE) endif() -add_executable(qclient client.cpp ) +add_executable(qclient client.cpp) -target_link_libraries(qclient LINK_PUBLIC quicr) +target_link_libraries(qclient PRIVATE quicr) + +if(NOT LIBQUICR_WITHOUT_INFLUXDB) + target_link_libraries(qclient + PRIVATE InfluxData::InfluxDB) +endif() target_compile_options(qclient PRIVATE $<$,$,$>: -Wpedantic -Wextra -Wall> $<$: >) + set_target_properties(qclient PROPERTIES CXX_STANDARD 20 diff --git a/cmd/moq-example/client.cpp b/cmd/moq-example/client.cpp index 1742d3f3..623c0f14 100644 --- a/cmd/moq-example/client.cpp +++ b/cmd/moq-example/client.cpp @@ -1,252 +1,90 @@ -#include -#include -#include -#include + +#include + +#include "signal_handler.h" #include #include -#include #include -class subDelegate : public quicr::SubscriberDelegate +class clientDelegate : public quicr::MoQInstanceDelegate { -public: - explicit subDelegate(cantina::LoggerPointer& logger) - : logger(std::make_shared("SDEL", logger)) - { - } - - void onSubscribeResponse( - [[maybe_unused]] const quicr::Namespace& quicr_namespace, - [[maybe_unused]] const quicr::SubscribeResult& result) override - { - logger->info << "onSubscriptionResponse: name: " << quicr_namespace << "/" - << int(quicr_namespace.length()) - << " status: " << static_cast(result.status) - << std::flush; - } - - void onSubscriptionEnded( - [[maybe_unused]] const quicr::Namespace& quicr_namespace, - [[maybe_unused]] const quicr::SubscribeResult::SubscribeStatus& reason) - override - { - logger->info << "onSubscriptionEnded: name: " << quicr_namespace << "/" - << static_cast(quicr_namespace.length()) - << std::flush; - } - - void onSubscribedObject([[maybe_unused]] const quicr::Name& quicr_name, - [[maybe_unused]] uint8_t priority, - [[maybe_unused]] quicr::bytes&& data) override - { - logger->info << "recv object: name: " << quicr_name - << " data sz: " << data.size(); - - if (!data.empty()) { - logger->info << " data: " << data.data(); - } - - logger->info << std::flush; - } - - void onSubscribedObjectFragment( - [[maybe_unused]] const quicr::Name& quicr_name, - [[maybe_unused]] uint8_t priority, - [[maybe_unused]] const uint64_t& offset, - [[maybe_unused]] bool is_last_fragment, - [[maybe_unused]] quicr::bytes&& data) override - { - } - -private: - cantina::LoggerPointer logger; + void cb_newConnection(qtransport::TransportConnId conn_id, + const std::span& endpoint_id, + const qtransport::TransportRemote& remote) override {} + void cb_connectionStatus(qtransport::TransportConnId conn_id, qtransport::TransportStatus status) override {} + void cb_clientSetup(qtransport::TransportConnId conn_id, quicr::messages::MoqClientSetup client_setup) override {} + void cb_serverSetup(qtransport::TransportConnId conn_id, quicr::messages::MoqServerSetup server_setup) override {} }; -class pubDelegate : public quicr::PublisherDelegate +int +main(int argc, char* argv[]) { -public: - std::atomic got_intent_response { false }; - - explicit pubDelegate(cantina::LoggerPointer& logger) - : logger(std::make_shared("PDEL", logger)) - { - } - - void onPublishIntentResponse( - const quicr::Namespace& quicr_namespace, - const quicr::PublishIntentResult& result) override - { - LOGGER_INFO(logger, - "Received PublishIntentResponse for " - << quicr_namespace << ": " - << static_cast(result.status)); - got_intent_response = true; - } - -private: - cantina::LoggerPointer logger; -}; + int result_code = EXIT_SUCCESS; -int do_publisher(cantina::LoggerPointer logger, - quicr::Client& client, - std::shared_ptr pd, - quicr::Name name) { + cantina::LoggerPointer logger = std::make_shared("qclient"); + logger->SetLogLevel("DEBUG"); - auto nspace = quicr::Namespace(name, 96); - logger->info << "Publish Intent for name: " << name - << " == namespace: " << nspace << std::flush; - - client.publishIntent(pd, nspace, {}, {}, {}, quicr::TransportMode::ReliablePerGroup, 2); - logger->info << "Waiting for intent response, up to 2.5 seconds" << std::flush; + if ((argc != 2) && (argc != 3)) { + std::cerr << "Relay address and port set in MOQ_RELAY and MOQ_PORT env variables." << std::endl; + std::cerr << std::endl; + std::cerr << "Usage publisher: qclient pub " << std::endl; + std::cerr << "Usage subscriber: qclient " << std::endl; + exit(-1); // NOLINT(concurrency-mt-unsafe) + } - for (int c=0; c < 50; c++) { - if (pd->got_intent_response) { - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds (50)); + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const auto* relayName = getenv("MOQ_RELAY"); + if (relayName == nullptr) { + relayName = "127.0.0.1"; } - if (!pd->got_intent_response) { - logger->info << "Did not receive publish intent, cannot proceed. Exit" << std::flush; - return -1; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const auto* portVar = getenv("MOQ_PORT"); + int port = 1234; + if (portVar != nullptr) { + port = atoi(portVar); // NOLINT(cert-err34-c) } - logger->info << "Received intent response." << std::flush; + // Install a signal handlers to catch operating system signals + installSignalHandlers(); - std::cout << "-----------------------------------------------------------------------" << std::endl; - std::cout << " Type a message and press ENTER to publish. Type the word exit to end program." << std::flush; - std::cout << "-----------------------------------------------------------------------" << std::endl; + // Lock the mutex so that main can then wait on it + std::unique_lock lock(moq_example::main_mutex); - while (true) { - std::string msg; - getline(std::cin, msg); - if (!msg.compare("exit")) { - logger->info << "Exit" << std::flush; - break; - } + quicr::MoQInstanceClientConfig config; + config.instance_id = "moq_client"; + config.server_host_ip = "127.0.0.1"; + config.server_port = 1234; + config.server_proto = qtransport::TransportProtocol::QUIC; + config.transport_config.debug = true; + config.transport_config.use_reset_wait_strategy = false; + config.transport_config.time_queue_max_duration = 5000; - logger->info << "Publish: " << msg << std::flush; - std::vector m_data(msg.begin(), msg.end()); + auto delegate = std::make_shared(); - std::vector trace; - const auto start_time = std::chrono::time_point_cast(std::chrono::steady_clock::now()); + try { + auto moqInstance = quicr::MoQInstance{config, delegate, logger}; - trace.push_back({"client:publish", start_time}); + moqInstance.run_client(); - client.publishNamedObject(name++, 0, 1000, std::move(m_data), std::move(trace)); - } + // Wait until told to terminate + moq_example::cv.wait(lock, [&]() { return moq_example::terminate; }); - return 0; -} + logger->info << "Client done" << std::endl; -int do_subscribe(cantina::LoggerPointer logger, - quicr::Client& client, - quicr::Name name) { - - auto sd = std::make_shared(logger); - auto nspace = quicr::Namespace(name, 96); - - logger->info << "Subscribe to " << name << "/" << 96 << std::flush; - - client.subscribe(sd, - nspace, - quicr::SubscribeIntent::immediate, - quicr::TransportMode::ReliablePerGroup, - "origin_url", - "auth_token", - quicr::bytes{}); - - logger->info << "Type exit to end program" << std::flush; - while (true) { - std::string msg; - getline(std::cin, msg); - if (!msg.compare("exit")) { - logger->info << "Exit" << std::flush; - break; - } + // Unlock the mutex + lock.unlock(); + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid argument: " << e.what() << std::endl; + result_code = EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << "Unexpected exception: " << e.what() << std::endl; + result_code = EXIT_FAILURE; + } catch (...) { + std::cerr << "Unexpected exception" << std::endl; + result_code = EXIT_FAILURE; } - - logger->Log("Now unsubscribing"); - client.unsubscribe(nspace, {}, {}); - - logger->Log("Sleeping for 5 seconds before exiting"); - std::this_thread::sleep_for(std::chrono::seconds(5)); - - return 0; -} - -int -main(int argc, char* argv[]) -{ - cantina::LoggerPointer logger = - std::make_shared("reallyTest"); - - if ((argc != 2) && (argc != 3)) { - std::cerr - << "Relay address and port set in REALLY_RELAY and REALLY_PORT env " - "variables." - << std::endl; - std::cerr << std::endl; - std::cerr << "Usage PUB: reallyTest FF0001 pub" << std::endl; - std::cerr << "Usage SUB: reallyTest FF0000" << std::endl; - exit(-1); // NOLINT(concurrency-mt-unsafe) - } - - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto* relayName = getenv("REALLY_RELAY"); - if (relayName == nullptr) { - relayName = "127.0.0.1"; - } - - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto* portVar = getenv("REALLY_PORT"); - int port = 1234; - if (portVar != nullptr) { - port = atoi(portVar); // NOLINT(cert-err34-c) - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - auto name = quicr::Name(std::string(argv[1])); - - logger->info << "Name = " << name << std::flush; - - auto data = std::vector{}; - if (argc == 3) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - const auto data_str = std::string(argv[2]); - data.insert(data.end(), data_str.begin(), data_str.end()); - } - - logger->info << "Connecting to " << relayName << ":" << port << std::flush; - - const auto relay = - quicr::RelayInfo{ .hostname = relayName, - .port = uint16_t(port), - .proto = quicr::RelayInfo::Protocol::QUIC }; - - const auto tcfg = qtransport::TransportConfig{ - .tls_cert_filename = nullptr, - .tls_key_filename = nullptr, - }; - - quicr::Client client(relay, "a@cisco.com", 0, tcfg, logger); - auto pd = std::make_shared(logger); - - if (!client.connect()) { - logger->Log(cantina::LogLevel::Critical, "Transport connect failed"); - return 0; - } - - if (!data.empty()) { - if (do_publisher(logger, client, pd, name)) - return -1; - - } else { - if (do_subscribe(logger, client, name)) - return -1; - } - - return 0; + return result_code; } diff --git a/cmd/moq-example/server.cpp b/cmd/moq-example/server.cpp index de2cd7f8..ff5ec72a 100644 --- a/cmd/moq-example/server.cpp +++ b/cmd/moq-example/server.cpp @@ -5,119 +5,9 @@ #include #include -#include "subscription.h" - - -// Module-level variables used to control program execution -namespace moq_server { - static std::mutex main_mutex; // Main's mutex - static bool terminate{ false }; // Termination flag - static std::condition_variable cv; // Main thread waits on this - static const char* termination_reason{ nullptr }; // Termination reason -} - -/* - * signalHandler - * - * Description: - * This function will handle operating system signals related to - * termination and then instruct the main thread to terminate. - * - * Parameters: - * signal_number [in] - * The signal caught. - * - * Returns: - * Nothing. - * - * Comments: - * None. - */ -void -signalHandler(int signal_number) -{ - const auto lock = std::lock_guard(moq_server::main_mutex); - - // If termination is in process, just return - if (moq_server::terminate) { - return; - } +#include "signal_handler.h" - // Indicate that the process should terminate - moq_server::terminate = true; - - // Set the termination reason string - switch (signal_number) { - case SIGINT: - moq_server::termination_reason = "Interrupt signal received"; - break; - -#ifndef _WIN32 - case SIGHUP: - moq_server::termination_reason = "Hangup signal received"; - break; - - case SIGQUIT: - moq_server::termination_reason = "Quit signal received"; - break; -#endif - - default: - moq_server::termination_reason = "Unknown signal received"; - break; - } - - // Notify the main execution thread to terminate - moq_server::cv.notify_one(); -} - -/* - * installSignalHandlers - * - * Description: - * This function will install the signal handlers for SIGINT, SIGQUIT, - * etc. so that the process can be terminated in a controlled fashion. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * None. - */ -void -installSignalHandlers() -{ -#ifdef _WIN32 - if (signal(SIGINT, signalHandler) == SIG_ERR) { - std::cerr << "Failed to install SIGINT handler" << std::endl; - } -#else - struct sigaction sa = {}; - - // Configure the sigaction struct - sa.sa_handler = signalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - - // Catch SIGHUP (signal 1) - if (sigaction(SIGHUP, &sa, nullptr) == -1) { - std::cerr << "Failed to install SIGHUP handler" << std::endl; - } - - // Catch SIGINT (signal 2) - if (sigaction(SIGINT, &sa, nullptr) == -1) { - std::cerr << "Failed to install SIGINT handler" << std::endl; - } - - // Catch SIGQUIT (signal 3) - if (sigaction(SIGQUIT, &sa, nullptr) == -1) { - std::cerr << "Failed to install SIGQUIT handler" << std::endl; - } -#endif -} +#include "subscription.h" class serverDelegate : public quicr::MoQInstanceDelegate { @@ -134,11 +24,14 @@ main() { int result_code = EXIT_SUCCESS; + auto logger = std::make_shared("qserver"); + logger->SetLogLevel("DEBUG"); + // Install a signal handlers to catch operating system signals installSignalHandlers(); // Lock the mutex so that main can then wait on it - std::unique_lock lock(moq_server::main_mutex); + std::unique_lock lock(moq_example::main_mutex); quicr::MoQInstanceServerConfig config; config.instance_id = "moq_server"; @@ -149,8 +42,6 @@ main() config.transport_config.tls_cert_filename = "./server-cert.pem"; config.transport_config.tls_key_filename = "./server-key.pem"; - auto logger = std::make_shared("moq_server"); - auto delegate = std::make_shared(); try { @@ -159,7 +50,7 @@ main() moqInstance.run_server(); // Wait until told to terminate - moq_server::cv.wait(lock, [&]() { return moq_server::terminate; }); + moq_example::cv.wait(lock, [&]() { return moq_example::terminate; }); // Unlock the mutex lock.unlock(); diff --git a/cmd/moq-example/subscription.h b/cmd/moq-example/subscription.h index a31e23a4..7cc3dc32 100644 --- a/cmd/moq-example/subscription.h +++ b/cmd/moq-example/subscription.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index a012e411..5c529105 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -6,14 +6,16 @@ #pragma once +#include #include #include -#include #include #include namespace quicr { + constexpr uint64_t MOQT_VERSION = 0xff000004; /// draft-ietf-moq-transport-04 + using namespace qtransport; struct MoQInstanceConfig @@ -25,7 +27,7 @@ namespace quicr { struct MoQInstanceClientConfig : MoQInstanceConfig { std::string server_host_ip; /// Relay hostname or IP to connect to - uint16_t server_port; /// Relay port to connect to + uint16_t server_port; /// Relay port to connect to TransportProtocol server_proto; /// Protocol to use when connecting to relay }; @@ -36,49 +38,12 @@ namespace quicr { TransportProtocol server_proto; /// Protocol to use }; - class MoQInstance; - /** - * @brief MOQ Instance Transport Delegate - * @details MOQ implements the transport delegate, which is shared by all sessions - */ - class MoQInstanceTransportDelegate : public ITransport::TransportDelegate - { - public: - MoQInstanceTransportDelegate(MoQInstance& moq_instance, const cantina::LoggerPointer& logger) - : _moq_instance(moq_instance) - , _logger(std::make_shared("MOQ_ITD", logger)) - { - } - - // ------------------------------------------------------------------------------------------------- - // Transprot Delegate/callback functions - // ------------------------------------------------------------------------------------------------- - - void on_new_data_context([[maybe_unused]] const TransportConnId& conn_id, - [[maybe_unused]] const DataContextId& data_ctx_id) override - { - } - - void on_connection_status(const TransportConnId& conn_id, const TransportStatus status) override; - void on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) override; - void on_recv_stream(const TransportConnId& conn_id, - uint64_t stream_id, - std::optional data_ctx_id, - const bool is_bidir = false) override; - void on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) override; - - private: - MoQInstance& _moq_instance; - cantina::LoggerPointer _logger; - }; - - /** * @brief MOQ Instance * @Details MoQ Instance is the handler for either a client or server. It can run * in only one mode, client or server. */ - class MoQInstance + class MoQInstance : public ITransport::TransportDelegate { public: enum class Status : uint8_t @@ -117,7 +82,7 @@ namespace quicr { std::shared_ptr delegate, const cantina::LoggerPointer& logger); - ~MoQInstance(); + ~MoQInstance() = default; // ------------------------------------------------------------------------------------------------- // Public API MoQ Intance API methods @@ -140,8 +105,7 @@ namespace quicr { * * @returns `track_alias` if no error and nullopt on error */ - std::optional publishTrack(TransportConnId conn_id, - std::shared_ptr track_delegate); + std::optional publishTrack(TransportConnId conn_id, std::shared_ptr track_delegate); /** * @brief Make client connection and run @@ -163,7 +127,6 @@ namespace quicr { */ Status run_server(); - /** * @brief Get the instance status * @@ -171,17 +134,57 @@ namespace quicr { */ Status status(); + /** + * Stop Instance + */ + void stop() { _stop = true; } + + // ------------------------------------------------------------------------------------------------- + // Transprot Delegate/callback functions + // ------------------------------------------------------------------------------------------------- + + void on_new_data_context([[maybe_unused]] const TransportConnId& conn_id, + [[maybe_unused]] const DataContextId& data_ctx_id) override + { + } + + void on_connection_status(const TransportConnId& conn_id, const TransportStatus status) override; + void on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) override; + void on_recv_stream(const TransportConnId& conn_id, + uint64_t stream_id, + std::optional data_ctx_id, + const bool is_bidir = false) override; + void on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) override; + private: + struct ConnectionContext + { + TransportConnId conn_id; + uint64_t ctrl_data_ctx_id; + std::optional msg_type_received; /// Indicates the current message type being read + }; + + // ------------------------------------------------------------------------------------------------- + // Private methods + // ------------------------------------------------------------------------------------------------- void init(); - private: + void send_ctrl_msg(const ConnectionContext& conn_ctx, std::vector&& data); + void send_client_setup(); + + // ------------------------------------------------------------------------------------------------- + // Private member variables + // ------------------------------------------------------------------------------------------------- + + std::mutex _mutex; const bool _client_mode; - std::atomic _stop{ false }; + bool _stop { false }; const MoQInstanceServerConfig _server_config; const MoQInstanceClientConfig _client_config; std::shared_ptr _delegate; - MoQInstanceTransportDelegate _transport_delegate; + + Status _status{ Status::NOT_READY }; // Log handler to use cantina::LoggerPointer _logger; @@ -192,8 +195,7 @@ namespace quicr { std::shared_ptr _transport; - std::vector _connections; + std::map _connections; }; - } // namespace quicr diff --git a/include/quicr/moq_instance_delegate.h b/include/quicr/moq_instance_delegate.h index 5a4e2e3c..210c4723 100644 --- a/include/quicr/moq_instance_delegate.h +++ b/include/quicr/moq_instance_delegate.h @@ -6,9 +6,11 @@ #pragma once +#include #include namespace quicr { + using namespace qtransport; /** * @brief MOQ/MOQT callbacks diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index eb32eeed..02d3f1f2 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -23,25 +23,32 @@ using TrackAlias = uint64_t; using ParamType = uint64_t; // Ref: https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-messages -constexpr uint8_t MESSAGE_TYPE_OBJECT_STREAM = 0x0; -constexpr uint8_t MESSAGE_TYPE_OBJECT_DATAGRAM = 0x1; -constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE = 0x3; -constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_OK = 0x4; -constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_ERROR = 0x5; -constexpr uint8_t MESSAGE_TYPE_ANNOUNCE = 0x6; -constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_OK = 0x7; -constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_ERROR = 0x8; -constexpr uint8_t MESSAGE_TYPE_UNANNOUNCE = 0x9; -constexpr uint8_t MESSAGE_TYPE_UNSUBSCRIBE = 0xA; -constexpr uint8_t MESSAGE_TYPE_SUBSCRIBE_DONE = 0xB; -constexpr uint8_t MESSAGE_TYPE_ANNOUNCE_CANCEL = 0xC; -constexpr uint8_t MESSAGE_TYPE_TRACK_STATUS_REQUEST = 0xD; -constexpr uint8_t MESSAGE_TYPE_TRACK_STATUS = 0xE; -constexpr uint8_t MESSAGE_TYPE_GOAWAY = 0x10; -constexpr uint8_t MESSAGE_TYPE_CLIENT_SETUP = 0x40; -constexpr uint8_t MESSAGE_TYPE_SERVER_SETUP = 0x41; -constexpr uint8_t MESSAGE_TYPE_STREAM_HEADER_TRACK = 0x50; -constexpr uint8_t MESSAGE_TYPE_STREAM_HEADER_GROUP = 0x51; +enum class MoQMessageType : uint64_t +{ + OBJECT_STREAM = 0x0, + OBJECT_DATAGRAM, + + SUBSCRIBE = 0x03, + SUBSCRIBE_OK, + SUBSCRIBE_ERROR, + ANNOUNCE, + ANNOUNCE_OK, + ANNOUNCE_ERROR, + UNANNOUNCE, + UNSUBSCRIBE, + SUBSCRIBE_DONE, + ANNOUNCE_CANCEL, + TRACK_STATUS_REQUEST, + TRACK_STATUS, + + GOWAY = 0x10, + + CLIENT_SETUP = 0x40, + SERVER_SETUP, + + STREAM_HEADER_TRACK = 0x50, + STREAM_HEADER_GROUP, +}; // TODO (Suhas): rename it to StreamMapping enum ForwardingPreference : uint8_t { @@ -335,5 +342,5 @@ struct MoqStreamGroupObject { uint64_t current_pos {0}; bool parse_completed { false }; }; - + } \ No newline at end of file diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp index 2055fd5f..6b58e9db 100644 --- a/src/moq_instance.cpp +++ b/src/moq_instance.cpp @@ -5,9 +5,12 @@ */ #include +#include namespace quicr { + using namespace quicr::messages; + MoQInstance::MoQInstance(const MoQInstanceClientConfig& cfg, std::shared_ptr delegate, const cantina::LoggerPointer& logger) : @@ -15,7 +18,6 @@ namespace quicr { , _server_config({}) , _client_config(cfg) , _delegate(std::move(delegate)) - , _transport_delegate({*this, _logger}) , _logger(std::make_shared("MOQ_IC", logger)) #ifndef LIBQUICR_WITHOUT_INFLUXDB , _mexport(logger) @@ -36,7 +38,6 @@ namespace quicr { , _server_config(cfg) , _client_config({}) , _delegate(std::move(delegate)) - , _transport_delegate({*this, _logger}) , _logger(std::make_shared("MOQ_IS", logger)) #ifndef LIBQUICR_WITHOUT_INFLUXDB , _mexport(logger) @@ -73,7 +74,7 @@ namespace quicr { .proto = _server_config.server_proto }; _transport = ITransport::make_server_transport(server, _server_config.transport_config, - _transport_delegate, _logger); + *this, _logger); #ifndef LIBQUICR_WITHOUT_INFLUXDB _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); @@ -96,69 +97,122 @@ namespace quicr { .proto = _client_config.server_proto }; _transport = ITransport::make_client_transport(relay, _client_config.transport_config, - _transport_delegate, _logger); + *this, _logger); + + _status = Status::CLIENT_CONNECTING; #ifndef LIBQUICR_WITHOUT_INFLUXDB - _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); + auto conn_id = _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); #else - _transport->start(nullptr, nullptr); + auto conn_id = _transport->start(nullptr, nullptr); #endif - _connections.push_back(_transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples)); + LOGGER_INFO(_logger, "Connecting session conn_id: " << conn_id << "..."); + _connections.try_emplace(conn_id, ConnectionContext{ .conn_id = conn_id }); - LOGGER_INFO(_logger, "Connecting session conn_id: " << _connections.front() << "..."); + return _status; + } - while (!_stop && _transport->status() == TransportStatus::Connecting) { - LOGGER_DEBUG(_logger, "Connecting... " << int(_stop.load())); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } + void MoQInstance::send_ctrl_msg(const ConnectionContext& conn_ctx, std::vector&& data) + { + _transport->enqueue(conn_ctx.conn_id, + conn_ctx.ctrl_data_ctx_id, + std::move(data), + { MethodTraceItem{} }, + 0, + 2000, + 0, + { true, false, false, false }); - if (_stop || !_transport) { - LOGGER_INFO(_logger, "Cancelling connecting session " << _connections.front()); - return Status::CLIENT_FAILED_TO_CONNECT; - } + } - if (_transport->status() != TransportStatus::Ready) { - _logger->error << "Failed to connect status: " << static_cast(_transport->status()) << std::flush; + void MoQInstance::send_client_setup() + { + StreamBuffer buffer; + auto client_setup = MoqClientSetup{}; - return Status::CLIENT_FAILED_TO_CONNECT; - } + client_setup.num_versions = 1; + client_setup.supported_versions = { MOQT_VERSION }; + client_setup.role_parameter.param_type = static_cast(ParameterType::Role); + client_setup.role_parameter.param_length = 0x1; // length of 1 for role value + client_setup.role_parameter.param_value = { 0x03 }; + + buffer << client_setup; + + auto &conn_ctx = _connections.begin()->second; - return Status::READY; + send_ctrl_msg(conn_ctx, buffer.front(buffer.size())); } - void MoQInstanceTransportDelegate::on_connection_status(const TransportConnId& conn_id, const TransportStatus status) + MoQInstance::Status MoQInstance::status() { - _logger->info << "Connection status conn_id: " << conn_id - << " status: " << static_cast(status) - << std::flush; + return _status; + } + + + // --------------------------------------------------------------------------------------- + // Transport delegate callbacks + // --------------------------------------------------------------------------------------- - _moq_instance.status(); + void MoQInstance::on_connection_status(const TransportConnId& conn_id, const TransportStatus status) + { + _logger->debug << "Connection status conn_id: " << conn_id + << " status: " << static_cast(status) + << std::flush; + + if (_client_mode) { + auto& conn_ctx = _connections[conn_id]; + _logger->info << "Connection established, creating bi-dir stream and sending CLIENT_SETUP" << std::flush; + + conn_ctx.ctrl_data_ctx_id = _transport->createDataContext(conn_id, true, 0, true); + send_client_setup(); + } + + _status = Status::READY; } - void MoQInstanceTransportDelegate::on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) + void MoQInstance::on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) { _logger->info << "New connection conn_id: " << conn_id << " remote ip: " << remote.host_or_ip << " port: " << remote.port << std::flush; - } - void MoQInstanceTransportDelegate::on_recv_stream(const TransportConnId& conn_id, + void MoQInstance::on_recv_stream(const TransportConnId& conn_id, uint64_t stream_id, - std::optional data_ctx_id, - const bool is_bidir) + [[maybe_unused]] std::optional data_ctx_id, + [[maybe_unused]] const bool is_bidir) { - _logger->info << "stream data conn_id: " << conn_id - << " stream_id: " << stream_id - << " data_ctx_id: " << (data_ctx_id ? *data_ctx_id : 0) - << " is_bidir: " << is_bidir - << std::flush; + auto stream_buf = _transport->getStreamBuffer(conn_id, stream_id); + auto& conn_ctx = _connections[conn_id]; + + if (stream_buf == nullptr) { + return; + } + + while (true) { + if (not conn_ctx.msg_type_received.has_value()) { + auto msg_type = stream_buf->decode_uintV(); + + if (msg_type) { + conn_ctx.msg_type_received = static_cast(*msg_type); + } else { + break; + } + } + + if (conn_ctx.msg_type_received) { + + // type is known, process data based on the message type + _logger->debug << "processing incoming message type: " + << static_cast(*conn_ctx.msg_type_received) << std::flush; + } + } } - void MoQInstanceTransportDelegate::on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) + void MoQInstance::on_recv_dgram(const TransportConnId& conn_id, std::optional data_ctx_id) { _logger->info << "datagram data conn_id: " << conn_id << " data_ctx_id: " << (data_ctx_id ? *data_ctx_id : 0) diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index 6b4f06c2..ca1968c2 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -111,7 +111,7 @@ MessageBuffer& operator>>(MessageBuffer &buffer, MoqParameter ¶m) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribe& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::SUBSCRIBE))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push_lv(msg.track_namespace); @@ -280,7 +280,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqUnsubscribe& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_UNSUBSCRIBE))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::UNSUBSCRIBE))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); return buffer; } @@ -296,7 +296,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqUnsubscribe &msg) qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribeDone& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::SUBSCRIBE_DONE))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.status_code)); buffer.push_lv(msg.reason_phrase); @@ -374,7 +374,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribeOk& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_OK))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::SUBSCRIBE_OK))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.expires)); msg.content_exists ? buffer.push(static_cast(1)) : buffer.push(static_cast(0)); @@ -443,7 +443,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqSubscribeError& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::SUBSCRIBE_ERROR))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.err_code)); buffer.push_lv(msg.reason_phrase); @@ -505,7 +505,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &ms qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounce& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::ANNOUNCE))); buffer.push_lv(msg.track_namespace); buffer.push(qtransport::to_uintV(static_cast(0))); return buffer; @@ -561,7 +561,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounceOk& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_OK))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::ANNOUNCE_OK))); buffer.push_lv(msg.track_namespace); return buffer; } @@ -583,7 +583,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceOk &msg) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounceError& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::ANNOUNCE_ERROR))); buffer.push_lv(msg.track_namespace.value()); buffer.push(qtransport::to_uintV(msg.err_code.value())); buffer.push_lv(msg.reason_phrase.value()); @@ -623,7 +623,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceError &msg qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqUnannounce& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_UNANNOUNCE))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::UNANNOUNCE))); buffer.push_lv(msg.track_namespace); return buffer; } @@ -644,7 +644,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqUnannounce &msg) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqAnnounceCancel& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::ANNOUNCE_CANCEL))); buffer.push_lv(msg.track_namespace); return buffer; } @@ -669,7 +669,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqAnnounceCancel &ms qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqGoaway& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_GOAWAY))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::GOWAY))); buffer.push_lv(msg.new_session_uri); return buffer; } @@ -687,7 +687,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqGoaway &msg) { MessageBuffer & operator<<(MessageBuffer &buffer, const MoqGoaway &msg) { - buffer << static_cast(MESSAGE_TYPE_GOAWAY); + buffer << static_cast(MoQMessageType::GOWAY); buffer << msg.new_session_uri; return buffer; } @@ -705,7 +705,7 @@ operator>>(MessageBuffer &buffer, MoqGoaway &msg) { qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqObjectStream& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_OBJECT_STREAM))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::OBJECT_STREAM))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push(qtransport::to_uintV(msg.group_id)); @@ -775,7 +775,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg) qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqObjectDatagram& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_OBJECT_DATAGRAM))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::OBJECT_DATAGRAM))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push(qtransport::to_uintV(msg.group_id)); @@ -846,7 +846,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectDatagram &ms qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqStreamHeaderTrack& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_STREAM_HEADER_TRACK))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::STREAM_HEADER_TRACK))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push(qtransport::to_uintV(msg.priority)); @@ -934,7 +934,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqStreamHeaderGroup& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_STREAM_HEADER_GROUP))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::STREAM_HEADER_GROUP))); buffer.push(qtransport::to_uintV(msg.subscribe_id)); buffer.push(qtransport::to_uintV(msg.track_alias)); buffer.push(qtransport::to_uintV(msg.group_id)); @@ -1022,7 +1022,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamGroupObject qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqClientSetup& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_CLIENT_SETUP))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::CLIENT_SETUP))); buffer.push(qtransport::to_uintV(static_cast(msg.supported_versions.size()))); // versions for (const auto& ver: msg.supported_versions) { @@ -1107,7 +1107,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqServerSetup& msg){ - buffer.push(qtransport::to_uintV(static_cast(MESSAGE_TYPE_SERVER_SETUP))); + buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::SERVER_SETUP))); buffer.push(qtransport::to_uintV(msg.selection_version)); /// num params @@ -1151,8 +1151,9 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) } msg.current_param->param_length = param->size(); msg.current_param->param_value = param.value(); - static_cast(msg.current_param->param_type) == ParameterType::Role ? - msg.role_parameter = std::move(msg.current_param.value()) : msg.path_parameter = std::move(msg.current_param.value()); + static_cast(msg.current_param->param_type) == ParameterType::Role + ? msg.role_parameter = std::move(msg.current_param.value()) + : msg.path_parameter = std::move(msg.current_param.value()); msg.current_param = MoqParameter{}; msg.num_params.value() -= 1; } diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 783699d2..bcfffb08 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -53,7 +53,7 @@ TEST_CASE("AnnounceOk Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqAnnounceOk announce_ok_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_OK), announce_ok_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::ANNOUNCE_OK), announce_ok_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); } @@ -67,7 +67,7 @@ TEST_CASE("Announce Message encode/decode") buffer << announce; std::vector net_data = buffer.front(buffer.size()); MoqAnnounce announce_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE), announce_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::ANNOUNCE), announce_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_out.track_namespace); CHECK_EQ(0, announce_out.params.size()); } @@ -82,7 +82,7 @@ TEST_CASE("Unannounce Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqAnnounceOk announce_ok_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_UNANNOUNCE), announce_ok_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::UNANNOUNCE), announce_ok_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_ok_out.track_namespace); } @@ -98,7 +98,7 @@ TEST_CASE("AnnounceError Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqAnnounceError announce_err_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_ERROR), announce_err_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::ANNOUNCE_ERROR), announce_err_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_err_out.track_namespace); CHECK_EQ(announce_err.err_code, announce_err_out.err_code); CHECK_EQ(announce_err.reason_phrase, announce_err_out.reason_phrase); @@ -114,7 +114,7 @@ TEST_CASE("AnnounceCancel Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqAnnounceCancel announce_cancel_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_ANNOUNCE_CANCEL), announce_cancel_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::ANNOUNCE_CANCEL), announce_cancel_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, announce_cancel_out.track_namespace); } @@ -135,7 +135,7 @@ TEST_CASE("Subscribe (LatestObject) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -161,7 +161,7 @@ TEST_CASE("Subscribe (LatestGroup) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -189,7 +189,7 @@ TEST_CASE("Subscribe (AbsoluteStart) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -222,7 +222,7 @@ TEST_CASE("Subscribe (AbsoluteRange) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -256,7 +256,7 @@ TEST_CASE("Subscribe (Params) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -297,7 +297,7 @@ TEST_CASE("Subscribe (Params - 2) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribe.subscribe_id, subscribe_out.subscribe_id); @@ -369,7 +369,7 @@ TEST_CASE("Subscribe (Combo) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribe subscribe_out; CHECK(verify( - net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE), subscribe_out)); + net_data, static_cast(MoQMessageType::SUBSCRIBE), subscribe_out)); CHECK_EQ(TRACK_NAMESPACE_CONF, subscribe_out.track_namespace); CHECK_EQ(TRACK_NAME_ALICE_VIDEO, subscribe_out.track_name); CHECK_EQ(subscribes[i].subscribe_id, subscribe_out.subscribe_id); @@ -402,7 +402,7 @@ TEST_CASE("SubscribeOk Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribeOk subscribe_ok_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_OK), subscribe_ok_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE_OK), subscribe_ok_out)); CHECK_EQ(subscribe_ok.subscribe_id, subscribe_ok_out.subscribe_id); CHECK_EQ(subscribe_ok.expires, subscribe_ok_out.expires); CHECK_EQ(subscribe_ok.content_exists, subscribe_ok_out.content_exists); @@ -424,7 +424,7 @@ TEST_CASE("SubscribeOk (content-exists) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribeOk subscribe_ok_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_OK), subscribe_ok_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE_OK), subscribe_ok_out)); CHECK_EQ(subscribe_ok.subscribe_id, subscribe_ok_out.subscribe_id); CHECK_EQ(subscribe_ok.expires, subscribe_ok_out.expires); CHECK_EQ(subscribe_ok.content_exists, subscribe_ok_out.content_exists); @@ -446,7 +446,7 @@ TEST_CASE("SubscribeError Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribeError subscribe_err_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_ERROR), subscribe_err_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE_ERROR), subscribe_err_out)); CHECK_EQ(subscribe_err.subscribe_id, subscribe_err_out.subscribe_id); CHECK_EQ(subscribe_err.err_code, subscribe_err_out.err_code); CHECK_EQ(subscribe_err.reason_phrase, subscribe_err_out.reason_phrase); @@ -464,7 +464,7 @@ TEST_CASE("Unsubscribe Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqUnsubscribe unsubscribe_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_UNSUBSCRIBE), unsubscribe_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::UNSUBSCRIBE), unsubscribe_out)); CHECK_EQ(unsubscribe.subscribe_id, unsubscribe_out.subscribe_id); } @@ -483,7 +483,7 @@ TEST_CASE("SubscribeDone Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribeDone subscribe_done_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE), subscribe_done_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE_DONE), subscribe_done_out)); CHECK_EQ(subscribe_done.subscribe_id, subscribe_done_out.subscribe_id); CHECK_EQ(subscribe_done.status_code, subscribe_done_out.status_code); CHECK_EQ(subscribe_done.reason_phrase, subscribe_done_out.reason_phrase); @@ -507,7 +507,7 @@ TEST_CASE("SubscribeDone (content-exists) Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqSubscribeDone subscribe_done_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SUBSCRIBE_DONE), subscribe_done_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SUBSCRIBE_DONE), subscribe_done_out)); CHECK_EQ(subscribe_done.subscribe_id, subscribe_done_out.subscribe_id); CHECK_EQ(subscribe_done.status_code, subscribe_done_out.status_code); CHECK_EQ(subscribe_done.reason_phrase, subscribe_done_out.reason_phrase); @@ -532,7 +532,7 @@ TEST_CASE("ClientSetup Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqClientSetup client_setup_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_CLIENT_SETUP), client_setup_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::CLIENT_SETUP), client_setup_out)); CHECK_EQ(client_setup.supported_versions, client_setup_out.supported_versions); CHECK_EQ(client_setup.role_parameter.param_value, client_setup_out.role_parameter.param_value); } @@ -552,7 +552,7 @@ TEST_CASE("ServerSetup Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqServerSetup server_setup_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_SERVER_SETUP), server_setup_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::SERVER_SETUP), server_setup_out)); CHECK_EQ(server_setup.selection_version, server_setup_out.selection_version); CHECK_EQ(server_setup.role_parameter.param_value, server_setup.role_parameter.param_value); } @@ -573,7 +573,7 @@ TEST_CASE("ObjectStream Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqObjectStream object_stream_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_OBJECT_STREAM), object_stream_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::OBJECT_STREAM), object_stream_out)); CHECK_EQ(object_stream.subscribe_id, object_stream_out.subscribe_id); CHECK_EQ(object_stream.track_alias, object_stream_out.track_alias); CHECK_EQ(object_stream.group_id, object_stream_out.group_id); @@ -598,7 +598,7 @@ TEST_CASE("ObjectDatagram Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqObjectStream object_datagram_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_OBJECT_DATAGRAM), object_datagram_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::OBJECT_DATAGRAM), object_datagram_out)); CHECK_EQ(object_datagram.subscribe_id, object_datagram_out.subscribe_id); CHECK_EQ(object_datagram.track_alias, object_datagram_out.track_alias); CHECK_EQ(object_datagram.group_id, object_datagram_out.group_id); @@ -620,7 +620,7 @@ TEST_CASE("StreamPerGroup Object Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqStreamHeaderGroup hdr_group_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_STREAM_HEADER_GROUP), hdr_group_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::STREAM_HEADER_GROUP), hdr_group_out)); CHECK_EQ(hdr_grp.subscribe_id, hdr_group_out.subscribe_id); CHECK_EQ(hdr_grp.track_alias, hdr_group_out.track_alias); CHECK_EQ(hdr_grp.group_id, hdr_group_out.group_id); @@ -671,7 +671,7 @@ TEST_CASE("StreamPerTrack Object Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqStreamHeaderTrack hdr_out; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_STREAM_HEADER_TRACK), hdr_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::STREAM_HEADER_TRACK), hdr_out)); CHECK_EQ(hdr_out.subscribe_id, hdr_out.subscribe_id); CHECK_EQ(hdr_out.track_alias, hdr_out.track_alias); CHECK_EQ(hdr_out.priority, hdr_out.priority); @@ -728,6 +728,6 @@ TEST_CASE("MoqGoaway Message encode/decode") std::vector net_data = buffer.front(buffer.size()); MoqGoaway goaway_out{}; - CHECK(verify(net_data, static_cast(MESSAGE_TYPE_GOAWAY), goaway_out)); + CHECK(verify(net_data, static_cast(MoQMessageType::GOWAY), goaway_out)); CHECK_EQ(from_ascii("go.away.now.no.return"), goaway_out.new_session_uri); } \ No newline at end of file From d710f514c4f48d89439c6503817aac0be3b93f26 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Fri, 7 Jun 2024 21:59:55 -0700 Subject: [PATCH 30/71] break out signal handler --- cmd/moq-example/signal_handler.h | 116 +++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 cmd/moq-example/signal_handler.h diff --git a/cmd/moq-example/signal_handler.h b/cmd/moq-example/signal_handler.h new file mode 100644 index 00000000..254465f0 --- /dev/null +++ b/cmd/moq-example/signal_handler.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include + +namespace moq_example { + std::mutex main_mutex; // Main's mutex + bool terminate{ false }; // Termination flag + std::condition_variable cv; // Main thread waits on this + const char* termination_reason{ nullptr }; // Termination reason +}; + +/* + * signalHandler + * + * Description: + * This function will handle operating system signals related to + * termination and then instruct the main thread to terminate. + * + * Parameters: + * signal_number [in] + * The signal caught. + * + * Returns: + * Nothing. + * + * Comments: + * None. + */ +void +signalHandler(int signal_number) +{ + const auto lock = std::lock_guard(moq_example::main_mutex); + + // If termination is in process, just return + if (moq_example::terminate) { + return; + } + + // Indicate that the process should terminate + moq_example::terminate = true; + + // Set the termination reason string + switch (signal_number) { + case SIGINT: + moq_example::termination_reason = "Interrupt signal received"; + break; + +#ifndef _WIN32 + case SIGHUP: + moq_example::termination_reason = "Hangup signal received"; + break; + + case SIGQUIT: + moq_example::termination_reason = "Quit signal received"; + break; +#endif + + default: + moq_example::termination_reason = "Unknown signal received"; + break; + } + + // Notify the main execution thread to terminate + moq_example::cv.notify_one(); +} + +/* + * installSignalHandlers + * + * Description: + * This function will install the signal handlers for SIGINT, SIGQUIT, + * etc. so that the process can be terminated in a controlled fashion. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * None. + */ +void +installSignalHandlers() +{ +#ifdef _WIN32 + if (signal(SIGINT, signalHandler) == SIG_ERR) { + std::cerr << "Failed to install SIGINT handler" << std::endl; + } +#else + struct sigaction sa = {}; + + // Configure the sigaction struct + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + // Catch SIGHUP (signal 1) + if (sigaction(SIGHUP, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGHUP handler" << std::endl; + } + + // Catch SIGINT (signal 2) + if (sigaction(SIGINT, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGINT handler" << std::endl; + } + + // Catch SIGQUIT (signal 3) + if (sigaction(SIGQUIT, &sa, nullptr) == -1) { + std::cerr << "Failed to install SIGQUIT handler" << std::endl; + } +#endif +} From b024e41b1c2c0c2b57e15917d5c11c7ea7bbc3ef Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Fri, 7 Jun 2024 22:00:45 -0700 Subject: [PATCH 31/71] update transport --- dependencies/transport | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/transport b/dependencies/transport index 3edbbdab..08c3f0d0 160000 --- a/dependencies/transport +++ b/dependencies/transport @@ -1 +1 @@ -Subproject commit 3edbbdab718b30a6fdbe9a092caf4e1459bb92c6 +Subproject commit 08c3f0d0647158fbff0ca95f028f3a47e2fec9bb From d21052b4b30fcf7ed0be4f34b003ac00ec8c671f Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Mon, 10 Jun 2024 21:55:27 -0700 Subject: [PATCH 32/71] MoQ updates... * Updated transport with bug fixes and new any data object in stream buffer * Implemented close with termination code/reason * Implemented bidir handling as first bidir stream and to close connection in error * Implemented CLIENT and SERVER SETUP state machine on new connections * Added MoQTerminationReason enum class * Resolved copy operator issues with moq structs * Disabled metrics exporter till we have endpoint_id added * Optimized moq message decode to continue to decode if stream buffer still has data. * Updated moq test `verify()` to use any object in stream buffer --- cmd/moq-example/server.cpp | 2 + dependencies/transport | 2 +- include/quicr/moq_instance.h | 9 +- include/quicr/moq_messages.h | 18 ++- src/moq_instance.cpp | 210 ++++++++++++++++++++++++++++++++--- src/moq_messages.cpp | 134 +++++++++++++--------- test/moq_messages.cpp | 14 ++- 7 files changed, 315 insertions(+), 74 deletions(-) diff --git a/cmd/moq-example/server.cpp b/cmd/moq-example/server.cpp index ff5ec72a..5e24878e 100644 --- a/cmd/moq-example/server.cpp +++ b/cmd/moq-example/server.cpp @@ -41,6 +41,8 @@ main() config.transport_config.debug = true; config.transport_config.tls_cert_filename = "./server-cert.pem"; config.transport_config.tls_key_filename = "./server-key.pem"; + config.transport_config.use_reset_wait_strategy = false; + config.transport_config.time_queue_max_duration = 5000; auto delegate = std::make_shared(); diff --git a/dependencies/transport b/dependencies/transport index 08c3f0d0..4430d5fc 160000 --- a/dependencies/transport +++ b/dependencies/transport @@ -1 +1 @@ -Subproject commit 08c3f0d0647158fbff0ca95f028f3a47e2fec9bb +Subproject commit 4430d5fc3125dec3e310772cd1cadbd8574e80ee diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index 5c529105..7f425ae1 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -160,7 +160,9 @@ namespace quicr { struct ConnectionContext { TransportConnId conn_id; - uint64_t ctrl_data_ctx_id; + std::optional ctrl_data_ctx_id; + bool setup_complete { false }; /// True if both client and server setup messages have completed + uint64_t client_version { 0 }; std::optional msg_type_received; /// Indicates the current message type being read }; @@ -171,6 +173,11 @@ namespace quicr { void send_ctrl_msg(const ConnectionContext& conn_ctx, std::vector&& data); void send_client_setup(); + void send_server_setup(ConnectionContext& conn_ctx); + void close_connection(TransportConnId conn_id, messages::MoQTerminationReason reason, + const std::string& reason_str); + bool process_recv_message(ConnectionContext& conn_ctx, + std::shared_ptr>& stream_buffer); // ------------------------------------------------------------------------------------------------- // Private member variables diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index 02d3f1f2..a4e045f4 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -22,6 +22,18 @@ using SubscribeId = uint64_t; using TrackAlias = uint64_t; using ParamType = uint64_t; +enum class MoQTerminationReason : uint64_t +{ + NO_ERROR = 0x0, + INTERNAL_ERROR, + UNAUTHORIZED, + PROTOCOL_VIOLATION, + DUP_TRACK_ALIAS, + PARAM_LEN_MISMATCH, + + GOAWAY_TIMEOUT = 0x10, +}; + // Ref: https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-messages enum class MoQMessageType : uint64_t { @@ -159,7 +171,7 @@ struct MoqSubscribeOk { const MoqSubscribeOk& msg); private: size_t current_pos {0}; - const size_t MAX_FIELDS {5}; + size_t MAX_FIELDS {5}; }; @@ -173,7 +185,7 @@ struct MoqSubscribeError { const MoqSubscribeError& msg); private: size_t current_pos {0}; - const size_t MAX_FIELDS {4}; + size_t MAX_FIELDS {4}; }; struct MoqUnsubscribe { @@ -196,7 +208,7 @@ struct MoqSubscribeDone const MoqSubscribeDone& msg); private: size_t current_pos {0}; - const size_t MAX_FIELDS = {6}; + size_t MAX_FIELDS = {6}; }; // diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp index 6b58e9db..d96c277f 100644 --- a/src/moq_instance.cpp +++ b/src/moq_instance.cpp @@ -77,7 +77,9 @@ namespace quicr { *this, _logger); #ifndef LIBQUICR_WITHOUT_INFLUXDB - _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); + // TODO(tievens) add back after endpoint_id is added + // _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); + _transport->start(nullptr, nullptr); #else _transport->start(nullptr, nullptr); #endif @@ -102,7 +104,9 @@ namespace quicr { _status = Status::CLIENT_CONNECTING; #ifndef LIBQUICR_WITHOUT_INFLUXDB - auto conn_id = _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); + // TODO(tievens): Add back after endpoint_id is added + //auto conn_id = _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); + auto conn_id = _transport->start(nullptr, nullptr); #else auto conn_id = _transport->start(nullptr, nullptr); #endif @@ -115,8 +119,15 @@ namespace quicr { void MoQInstance::send_ctrl_msg(const ConnectionContext& conn_ctx, std::vector&& data) { + if (not conn_ctx.ctrl_data_ctx_id) { + close_connection(conn_ctx.conn_id, + MoQTerminationReason::PROTOCOL_VIOLATION, + "Control bidir stream not created"); + return; + } + _transport->enqueue(conn_ctx.conn_id, - conn_ctx.ctrl_data_ctx_id, + *conn_ctx.ctrl_data_ctx_id, std::move(data), { MethodTraceItem{} }, 0, @@ -131,10 +142,10 @@ namespace quicr { StreamBuffer buffer; auto client_setup = MoqClientSetup{}; - client_setup.num_versions = 1; + client_setup.num_versions = 1; // NOTE: Not used for encode, verison vector size is used client_setup.supported_versions = { MOQT_VERSION }; client_setup.role_parameter.param_type = static_cast(ParameterType::Role); - client_setup.role_parameter.param_length = 0x1; // length of 1 for role value + client_setup.role_parameter.param_length = 0x1; // NOTE: not used for encode, size of value is used client_setup.role_parameter.param_value = { 0x03 }; buffer << client_setup; @@ -144,12 +155,134 @@ namespace quicr { send_ctrl_msg(conn_ctx, buffer.front(buffer.size())); } + void MoQInstance::send_server_setup(ConnectionContext& conn_ctx) + { + StreamBuffer buffer; + auto server_setup = MoqServerSetup{}; + + server_setup.selection_version = { conn_ctx.client_version }; + server_setup.role_parameter.param_type = static_cast(ParameterType::Role); + server_setup.role_parameter.param_length = 0x1; // NOTE: not used for encode, size of value is used + server_setup.role_parameter.param_value = { 0x03 }; + + buffer << server_setup; + + _logger->info << "Sending SERVER_SETUP to conn_id: " << conn_ctx.conn_id << std::flush; + + send_ctrl_msg(conn_ctx, buffer.front(buffer.size())); + } + MoQInstance::Status MoQInstance::status() { return _status; } + bool MoQInstance::process_recv_message(ConnectionContext& conn_ctx, + std::shared_ptr>& stream_buffer) + { + if (stream_buffer->size() == 0) { // should never happen + close_connection(conn_ctx.conn_id, + MoQTerminationReason::INTERNAL_ERROR, + "Stream buffer cannot be zero when parsing message type"); + } + + if (not conn_ctx.msg_type_received) { // should never happen + close_connection(conn_ctx.conn_id, + MoQTerminationReason::INTERNAL_ERROR, + "Process recv message connection context is missing message type"); + } + + switch (*conn_ctx.msg_type_received) { + case MoQMessageType::OBJECT_STREAM: + break; + case MoQMessageType::OBJECT_DATAGRAM: + break; + case MoQMessageType::SUBSCRIBE: + break; + case MoQMessageType::SUBSCRIBE_OK: + break; + case MoQMessageType::SUBSCRIBE_ERROR: + break; + case MoQMessageType::ANNOUNCE: + break; + case MoQMessageType::ANNOUNCE_OK: + break; + case MoQMessageType::ANNOUNCE_ERROR: + break; + case MoQMessageType::UNANNOUNCE: + break; + case MoQMessageType::UNSUBSCRIBE: + break; + case MoQMessageType::SUBSCRIBE_DONE: + break; + case MoQMessageType::ANNOUNCE_CANCEL: + break; + case MoQMessageType::TRACK_STATUS_REQUEST: + break; + case MoQMessageType::TRACK_STATUS: + break; + case MoQMessageType::GOWAY: + break; + case MoQMessageType::CLIENT_SETUP: { + if (not stream_buffer->anyHasValue()) { + _logger->info << "init stream buffer client setup" << std::flush; + stream_buffer->initAny(); + } + + auto& msg = stream_buffer->getAny(); + if (*stream_buffer >> msg) { + if (!msg.supported_versions.size()) { // should never happen + close_connection(conn_ctx.conn_id, + MoQTerminationReason::PROTOCOL_VIOLATION, + "Client setup contained zero versions"); + + } + + _logger->info << "Client setup received, " + << " num_versions: " << msg.num_versions + << " role: " << static_cast(msg.role_parameter.param_value.front()) + << " version: 0x" << std::hex << msg.supported_versions.front() + << std::dec << std::flush; + + conn_ctx.client_version = msg.supported_versions.front(); + stream_buffer->resetAny(); + + send_server_setup(conn_ctx); + conn_ctx.setup_complete = true; + return true; + } + break; + } + case MoQMessageType::SERVER_SETUP: { + if (not stream_buffer->anyHasValue()) { + _logger->info << "init stream buffer server setup" << std::flush; + stream_buffer->initAny(); + } + + auto& msg = stream_buffer->getAny(); + if (*stream_buffer >> msg) { + _logger->info << "Server setup received," + << " role: " << static_cast(msg.role_parameter.param_value.front()) + << " selected_version: 0x" << std::hex << msg.selection_version + << std::dec << std::flush; + stream_buffer->resetAny(); + conn_ctx.setup_complete = true; + return true; + } + break; + } + case MoQMessageType::STREAM_HEADER_TRACK: + break; + case MoQMessageType::STREAM_HEADER_GROUP: + break; + } + + _logger->debug << " type: " << static_cast(*conn_ctx.msg_type_received) + << " sbuf_size: " << stream_buffer->size() + << std::flush; + return false; + } // --------------------------------------------------------------------------------------- // Transport delegate callbacks @@ -174,16 +307,20 @@ namespace quicr { void MoQInstance::on_new_connection(const TransportConnId& conn_id, const TransportRemote& remote) { + auto [conn_ctx, is_new] = _connections.try_emplace(conn_id, ConnectionContext{}); + _logger->info << "New connection conn_id: " << conn_id << " remote ip: " << remote.host_or_ip << " port: " << remote.port << std::flush; + + conn_ctx->second.conn_id = conn_id; } void MoQInstance::on_recv_stream(const TransportConnId& conn_id, uint64_t stream_id, - [[maybe_unused]] std::optional data_ctx_id, - [[maybe_unused]] const bool is_bidir) + std::optional data_ctx_id, + const bool is_bidir) { auto stream_buf = _transport->getStreamBuffer(conn_id, stream_id); auto& conn_ctx = _connections[conn_id]; @@ -192,8 +329,18 @@ namespace quicr { return; } - while (true) { - if (not conn_ctx.msg_type_received.has_value()) { + if (is_bidir && not conn_ctx.ctrl_data_ctx_id) { + if (not data_ctx_id) { + close_connection(conn_id, + MoQTerminationReason::INTERNAL_ERROR, + "Received bidir is missing data context"); + return; + } + conn_ctx.ctrl_data_ctx_id = data_ctx_id; + } + + while (stream_buf->size()) { + if (not conn_ctx.msg_type_received) { auto msg_type = stream_buf->decode_uintV(); if (msg_type) { @@ -204,10 +351,9 @@ namespace quicr { } if (conn_ctx.msg_type_received) { - - // type is known, process data based on the message type - _logger->debug << "processing incoming message type: " - << static_cast(*conn_ctx.msg_type_received) << std::flush; + if (process_recv_message(conn_ctx, stream_buf)) { + conn_ctx.msg_type_received = std::nullopt; + } } } } @@ -220,5 +366,43 @@ namespace quicr { } + void MoQInstance::close_connection(TransportConnId conn_id, messages::MoQTerminationReason reason, + const std::string& reason_str) + { + _logger->info << "Closing conn_id: " << conn_id; + switch (reason) { + case MoQTerminationReason::NO_ERROR: + _logger->info << " no error"; + break; + case MoQTerminationReason::INTERNAL_ERROR: + _logger->info << " internal error: " << reason_str; + break; + case MoQTerminationReason::UNAUTHORIZED: + _logger->info << " unauthorized: " << reason_str; + break; + case MoQTerminationReason::PROTOCOL_VIOLATION: + _logger->info << " protocol violation: " << reason_str; + break; + case MoQTerminationReason::DUP_TRACK_ALIAS: + _logger->info << " duplicate track alias: " << reason_str; + break; + case MoQTerminationReason::PARAM_LEN_MISMATCH: + _logger->info << " param length mismatch: " << reason_str; + break; + case MoQTerminationReason::GOAWAY_TIMEOUT: + _logger->info << " goaway timeout: " << reason_str; + break; + } + _logger->info << std::flush; + + // TODO(tievens): update transport to allow passing the reason/error code value + _transport->close(conn_id); + + if (_client_mode) { + _logger->info << "Client connection closed, stopping client" << std::flush; + _stop = true; + } + } + } // namespace quicr diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index ca1968c2..bd681e97 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -154,29 +154,29 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!parse_bytes_field(buffer, msg.track_namespace)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { if(!parse_bytes_field(buffer, msg.track_name)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 4: { const auto val = buffer.decode_uintV(); if (!val) { @@ -191,8 +191,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { } else { msg.current_pos += 1; } + [[fallthrough]]; } - break; case 5: { if (msg.filter_type == FilterType::AbsoluteStart || msg.filter_type == FilterType::AbsoluteRange) { @@ -201,8 +201,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { } msg.current_pos += 1; } + [[fallthrough]]; } - break; case 6: { if (msg.filter_type == FilterType::AbsoluteStart || msg.filter_type == FilterType::AbsoluteRange) { @@ -216,8 +216,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { msg.current_pos += 1; } } + [[fallthrough]]; } - break; case 7: { if (msg.filter_type == FilterType::AbsoluteRange) { if (!parse_uintV_field(buffer, msg.end_group)) { @@ -225,8 +225,9 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { } msg.current_pos += 1; } + + [[fallthrough]]; } - break; case 8: { if (msg.filter_type == FilterType::AbsoluteRange) { if (!parse_uintV_field(buffer, msg.end_object)) { @@ -234,8 +235,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { } msg.current_pos += 1; } + [[fallthrough]]; } - break; case 9: { if (!msg.current_param.has_value()) { if (!parse_uintV_field(buffer, msg.num_params)) { @@ -266,8 +267,11 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { msg.num_params -= 1; } msg.parsing_completed = true; + [[fallthrough]]; } - break; + + default: + break; } if (!msg.parsing_completed ) { @@ -318,15 +322,15 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.status_code)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { const auto val = buffer.decode_bytes(); if (!val) { @@ -334,8 +338,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg } msg.reason_phrase = std::move(val.value()); msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { const auto val = buffer.front(); if (!val) { @@ -348,22 +352,24 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeDone &msg // nothing more to process. return true; } + [[fallthrough]]; } - break; case 4: { if(!parse_uintV_field(buffer, msg.final_group_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 5: { if(!parse_uintV_field(buffer, msg.final_object_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; + default: + break; } if (msg.current_pos < msg.MAX_FIELDS) { @@ -395,15 +401,15 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.expires)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { const auto val = buffer.front(); if (!val) { @@ -416,22 +422,24 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeOk &msg) // nothing more to process. return true; } + [[fallthrough]]; } - break; case 3: { if(!parse_uintV_field(buffer, msg.largest_group)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 4: { if(!parse_uintV_field(buffer, msg.largest_object)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; + default: + break; } if (msg.current_pos < msg.MAX_FIELDS) { @@ -461,15 +469,15 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &ms return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.err_code)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { const auto val = buffer.decode_bytes(); if (!val) { @@ -477,15 +485,17 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribeError &ms } msg.reason_phrase = std::move(val.value()); msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; + default: + break; } if (msg.current_pos < msg.MAX_FIELDS) { @@ -723,36 +733,36 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg) return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!parse_uintV_field(buffer, msg.group_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { if(!parse_uintV_field(buffer, msg.object_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 4: { if(!parse_uintV_field(buffer, msg.priority)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 5: { const auto val = buffer.decode_bytes(); if (!val) { @@ -760,8 +770,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectStream &msg) } msg.payload = std::move(val.value()); msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -793,36 +805,36 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectDatagram &ms return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!parse_uintV_field(buffer, msg.group_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { if(!parse_uintV_field(buffer, msg.object_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 4: { if(!parse_uintV_field(buffer, msg.priority)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 5: { const auto val = buffer.decode_bytes(); if (!val) { @@ -830,8 +842,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqObjectDatagram &ms } msg.payload = std::move(val.value()); msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -861,23 +875,25 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderTrack return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!parse_uintV_field(buffer, msg.priority)) { return false; } msg.current_pos += 1; msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -903,15 +919,15 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.object_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { const auto val = buffer.decode_bytes(); if(!val) { @@ -919,8 +935,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamTrackObject } msg.payload = std::move(val.value()); msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -949,30 +967,32 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamHeaderGroup return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { if(!parse_uintV_field(buffer, msg.track_alias)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!parse_uintV_field(buffer, msg.group_id)) { return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 3: { if(!parse_uintV_field(buffer, msg.priority)) { return false; } msg.current_pos += 1; msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -998,8 +1018,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamGroupObject return false; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 1: { const auto val = buffer.decode_bytes(); if (!val) { @@ -1007,8 +1027,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqStreamGroupObject } msg.payload = std::move(val.value()); msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if(!msg.parse_completed) { @@ -1023,7 +1045,7 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& const MoqClientSetup& msg){ buffer.push(qtransport::to_uintV(static_cast(MoQMessageType::CLIENT_SETUP))); - buffer.push(qtransport::to_uintV(static_cast(msg.supported_versions.size()))); + buffer.push(qtransport::to_uintV(msg.supported_versions.size())); // versions for (const auto& ver: msg.supported_versions) { buffer.push(qtransport::to_uintV(ver)); @@ -1044,6 +1066,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) return false; } msg.current_pos += 1; + [[fallthrough]]; } case 1: { while (msg.num_versions > 0) { @@ -1055,8 +1078,8 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) msg.num_versions -= 1; } msg.current_pos += 1; + [[fallthrough]]; } - break; case 2: { if(!msg.num_params.has_value()) { auto params = uint64_t {0}; @@ -1089,8 +1112,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) } msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if (!msg.parse_completed) { @@ -1125,6 +1150,7 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) return false; } msg.current_pos += 1; + [[fallthrough]]; } case 1: { if(!msg.num_params.has_value()) { @@ -1158,8 +1184,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) msg.num_params.value() -= 1; } msg.parse_completed = true; + [[fallthrough]]; } - break; + default: + break; } if (!msg.parse_completed) { diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index bcfffb08..85583f3d 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -1,9 +1,10 @@ +#include #include +#include #include +#include #include #include -#include -#include using namespace quicr; using namespace quicr::messages; @@ -21,10 +22,15 @@ template bool verify(std::vector& net_data, uint64_t message_type, T& message, size_t slice_depth=1) { // TODO: support size_depth > 1, if needed qtransport::StreamBuffer in_buffer; + in_buffer.initAny(); // Set parsed data to be of this type using out param + std::optional msg_type; bool done = false; + for (auto& v: net_data) { + auto &msg = in_buffer.getAny(); in_buffer.push(v); + if (!msg_type) { msg_type = in_buffer.decode_uintV(); if (!msg_type) { @@ -34,8 +40,10 @@ bool verify(std::vector& net_data, uint64_t message_type, T& message, s continue; } - done = in_buffer >> message; + done = in_buffer >> msg; if (done) { + // copy the working parsed data to out param. + message = msg; break; } } From 186410923c0dd44f38bc744e4587020e9aac5d63 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Mon, 10 Jun 2024 22:08:36 -0700 Subject: [PATCH 33/71] Update track delegate to support updating priority on sendObject --- include/quicr/moq_track_delegate.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/quicr/moq_track_delegate.h b/include/quicr/moq_track_delegate.h index 5b8f2538..08ac0f35 100644 --- a/include/quicr/moq_track_delegate.h +++ b/include/quicr/moq_track_delegate.h @@ -89,6 +89,8 @@ namespace quicr { */ SendError sendObject(const std::span& object); SendError sendObject(const std::span& object, uint32_t ttl); + SendError sendObject(const std::span& object, uint8_t priority); + SendError sendObject(const std::span& object, uint8_t priority, uint32_t ttl); /** * @brief Read object from track From 97df872f54617766fb16c1cd58e33b7dae3f4428 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Tue, 11 Jun 2024 10:51:23 -0700 Subject: [PATCH 34/71] Update close() to include app_reason_code --- dependencies/transport | 2 +- src/moq_instance.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies/transport b/dependencies/transport index 4430d5fc..645e036f 160000 --- a/dependencies/transport +++ b/dependencies/transport @@ -1 +1 @@ -Subproject commit 4430d5fc3125dec3e310772cd1cadbd8574e80ee +Subproject commit 645e036fc4a0ac506829899641c6b102a1211121 diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp index d96c277f..9997c5d7 100644 --- a/src/moq_instance.cpp +++ b/src/moq_instance.cpp @@ -268,6 +268,7 @@ namespace quicr { << std::dec << std::flush; stream_buffer->resetAny(); conn_ctx.setup_complete = true; + return true; } break; @@ -395,8 +396,7 @@ namespace quicr { } _logger->info << std::flush; - // TODO(tievens): update transport to allow passing the reason/error code value - _transport->close(conn_id); + _transport->close(conn_id, static_cast(reason)); if (_client_mode) { _logger->info << "Client connection closed, stopping client" << std::flush; From 6d52a9cf67da84096408559a74cbf3f0bc9e5d39 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Tue, 11 Jun 2024 17:27:02 -0700 Subject: [PATCH 35/71] MOQ update to add metrics and endpoint_id * Fixes parameter parsing of type not being uintV * Adds endpoint_id parameter type * Implement metrics connection and data context info updates for control channel. * Re-enabled metrics --- cmd/moq-example/client.cpp | 2 +- cmd/moq-example/server.cpp | 2 +- include/quicr/moq_instance.h | 2 +- include/quicr/moq_messages.h | 5 +- src/moq_instance.cpp | 49 ++++++++++--- src/moq_messages.cpp | 132 ++++++++++++++++++++++------------- test/fake_transport.h | 2 +- test/moq_messages.cpp | 7 ++ 8 files changed, 141 insertions(+), 60 deletions(-) diff --git a/cmd/moq-example/client.cpp b/cmd/moq-example/client.cpp index 623c0f14..094d98ff 100644 --- a/cmd/moq-example/client.cpp +++ b/cmd/moq-example/client.cpp @@ -53,7 +53,7 @@ main(int argc, char* argv[]) std::unique_lock lock(moq_example::main_mutex); quicr::MoQInstanceClientConfig config; - config.instance_id = "moq_client"; + config.endpoint_id = "moq_client"; config.server_host_ip = "127.0.0.1"; config.server_port = 1234; config.server_proto = qtransport::TransportProtocol::QUIC; diff --git a/cmd/moq-example/server.cpp b/cmd/moq-example/server.cpp index 5e24878e..ee189862 100644 --- a/cmd/moq-example/server.cpp +++ b/cmd/moq-example/server.cpp @@ -34,7 +34,7 @@ main() std::unique_lock lock(moq_example::main_mutex); quicr::MoQInstanceServerConfig config; - config.instance_id = "moq_server"; + config.endpoint_id = "moq_server"; config.server_bind_ip = "127.0.0.1"; config.server_port = 1234; config.server_proto = qtransport::TransportProtocol::QUIC; diff --git a/include/quicr/moq_instance.h b/include/quicr/moq_instance.h index 7f425ae1..1ee2dfe8 100644 --- a/include/quicr/moq_instance.h +++ b/include/quicr/moq_instance.h @@ -20,7 +20,7 @@ namespace quicr { struct MoQInstanceConfig { - std::string instance_id; /// Instance ID for the client or server, should be unique + std::string endpoint_id; /// Endpoint ID for the client or server, should be unique TransportConfig transport_config; }; diff --git a/include/quicr/moq_messages.h b/include/quicr/moq_messages.h index a4e045f4..f2028277 100644 --- a/include/quicr/moq_messages.h +++ b/include/quicr/moq_messages.h @@ -80,6 +80,7 @@ enum struct ParameterType : uint8_t { Role = 0x0, Path = 0x1, AuthorizationInfo = 0x2, // version specific, unused + EndpointId = 0xF0, // Endpoint ID, using temp value for now Invalid = 0xFF, // used internally. }; @@ -103,6 +104,7 @@ struct MoqClientSetup { std::vector supported_versions; MoqParameter role_parameter; MoqParameter path_parameter; + MoqParameter endpoint_id_parameter; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqClientSetup& msg); @@ -117,6 +119,7 @@ struct MoqServerSetup { Version selection_version; MoqParameter role_parameter; MoqParameter path_parameter; + MoqParameter endpoint_id_parameter; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, const MoqServerSetup& msg); @@ -149,7 +152,7 @@ struct MoqSubscribe { uint64_t end_group {0}; uint64_t start_object {0}; uint64_t end_object {0}; - uint64_t num_params {0}; + std::optional num_params; std::vector track_params; friend bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg); friend qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer, diff --git a/src/moq_instance.cpp b/src/moq_instance.cpp index 9997c5d7..4f3c75a5 100644 --- a/src/moq_instance.cpp +++ b/src/moq_instance.cpp @@ -77,9 +77,7 @@ namespace quicr { *this, _logger); #ifndef LIBQUICR_WITHOUT_INFLUXDB - // TODO(tievens) add back after endpoint_id is added - // _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); - _transport->start(nullptr, nullptr); + _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); #else _transport->start(nullptr, nullptr); #endif @@ -104,9 +102,7 @@ namespace quicr { _status = Status::CLIENT_CONNECTING; #ifndef LIBQUICR_WITHOUT_INFLUXDB - // TODO(tievens): Add back after endpoint_id is added - //auto conn_id = _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); - auto conn_id = _transport->start(nullptr, nullptr); + auto conn_id = _transport->start(_mexport.metrics_conn_samples, _mexport.metrics_data_samples); #else auto conn_id = _transport->start(nullptr, nullptr); #endif @@ -147,6 +143,8 @@ namespace quicr { client_setup.role_parameter.param_type = static_cast(ParameterType::Role); client_setup.role_parameter.param_length = 0x1; // NOTE: not used for encode, size of value is used client_setup.role_parameter.param_value = { 0x03 }; + client_setup.endpoint_id_parameter.param_value.assign(_client_config.endpoint_id.begin(), + _client_config.endpoint_id.end()); buffer << client_setup; @@ -164,6 +162,9 @@ namespace quicr { server_setup.role_parameter.param_type = static_cast(ParameterType::Role); server_setup.role_parameter.param_length = 0x1; // NOTE: not used for encode, size of value is used server_setup.role_parameter.param_value = { 0x03 }; + server_setup.endpoint_id_parameter.param_value.assign(_server_config.endpoint_id.begin(), + _server_config.endpoint_id.end()); + buffer << server_setup; @@ -239,7 +240,9 @@ namespace quicr { } - _logger->info << "Client setup received, " + std::string client_endpoint_id(msg.endpoint_id_parameter.param_value.begin(), + msg.endpoint_id_parameter.param_value.end()); + _logger->info << "Client setup received from: " << client_endpoint_id << " num_versions: " << msg.num_versions << " role: " << static_cast(msg.role_parameter.param_value.front()) << " version: 0x" << std::hex << msg.supported_versions.front() @@ -248,6 +251,14 @@ namespace quicr { conn_ctx.client_version = msg.supported_versions.front(); stream_buffer->resetAny(); +#ifndef LIBQUICR_WITHOUT_INFLUXDB + _mexport.set_conn_ctx_info(conn_ctx.conn_id, {.endpoint_id = client_endpoint_id, + .relay_id = _server_config.endpoint_id, + .data_ctx_info = {}}, false); + _mexport.set_data_ctx_info(conn_ctx.conn_id, *conn_ctx.ctrl_data_ctx_id, + {.subscribe = false, .nspace = {}}); +#endif + send_server_setup(conn_ctx); conn_ctx.setup_complete = true; return true; @@ -262,13 +273,30 @@ namespace quicr { auto& msg = stream_buffer->getAny(); if (*stream_buffer >> msg) { - _logger->info << "Server setup received," + std::string server_endpoint_id(msg.endpoint_id_parameter.param_value.begin(), + msg.endpoint_id_parameter.param_value.end()); + + _logger->info << "Server setup received" + << " from: " << server_endpoint_id + << std::string(msg.endpoint_id_parameter.param_value.begin(), + msg.endpoint_id_parameter.param_value.end()) << " role: " << static_cast(msg.role_parameter.param_value.front()) << " selected_version: 0x" << std::hex << msg.selection_version << std::dec << std::flush; + +#ifndef LIBQUICR_WITHOUT_INFLUXDB + _mexport.set_conn_ctx_info(conn_ctx.conn_id, {.endpoint_id = server_endpoint_id, + .relay_id = _server_config.endpoint_id, + .data_ctx_info = {}}, false); + + _mexport.set_data_ctx_info(conn_ctx.conn_id, *conn_ctx.ctrl_data_ctx_id, + {.subscribe = false, .nspace = {}}); +#endif + stream_buffer->resetAny(); conn_ctx.setup_complete = true; + return true; } break; @@ -300,6 +328,11 @@ namespace quicr { _logger->info << "Connection established, creating bi-dir stream and sending CLIENT_SETUP" << std::flush; conn_ctx.ctrl_data_ctx_id = _transport->createDataContext(conn_id, true, 0, true); +#ifndef LIBQUICR_WITHOUT_INFLUXDB + _mexport.set_data_ctx_info(conn_ctx.conn_id, *conn_ctx.ctrl_data_ctx_id, + {.subscribe = false, .nspace = {}}); +#endif + send_client_setup(); } diff --git a/src/moq_messages.cpp b/src/moq_messages.cpp index bd681e97..e077008a 100644 --- a/src/moq_messages.cpp +++ b/src/moq_messages.cpp @@ -136,11 +136,10 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& break; } - buffer.push(qtransport::to_uintV(msg.num_params)); + buffer.push(qtransport::to_uintV(msg.track_params.size())); for (const auto& param: msg.track_params) { buffer.push(qtransport::to_uintV(static_cast(param.param_type))); - buffer.push(qtransport::to_uintV(param.param_length)); - buffer.push(param.param_value); + buffer.push_lv(param.param_value); } return buffer; @@ -238,21 +237,24 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { [[fallthrough]]; } case 9: { - if (!msg.current_param.has_value()) { - if (!parse_uintV_field(buffer, msg.num_params)) { + if (!msg.num_params.has_value()) { + uint64_t num = 0; + if (!parse_uintV_field(buffer, num)) { return false; } - msg.current_param = MoqParameter{}; + + msg.num_params = num; } // parse each param - while (msg.num_params > 0) { - if (!msg.current_param.value().param_type) { - auto val = buffer.front(); - if (!val) { + while (*msg.num_params > 0) { + if (!msg.current_param.has_value()) { + uint64_t type {0}; + if (!parse_uintV_field(buffer, type)) { return false; } - msg.current_param.value().param_type = *val; - buffer.pop(); + + msg.current_param = MoqParameter{}; + msg.current_param->param_type = type; } // decode param_len: @@ -263,9 +265,10 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqSubscribe &msg) { msg.current_param.value().param_length = param->size(); msg.current_param.value().param_value = param.value(); msg.track_params.push_back(msg.current_param.value()); - msg.current_param = MoqParameter{}; - msg.num_params -= 1; + msg.current_param = std::nullopt; + *msg.num_params -= 1; } + msg.parsing_completed = true; [[fallthrough]]; } @@ -545,12 +548,13 @@ bool operator>>(qtransport::StreamBuffer &buffer, // parse each param while (msg.num_params > 0) { if (!msg.current_param.param_type) { - auto val = buffer.front(); - if (!val) { + uint64_t type {0}; + if (!parse_uintV_field(buffer, type)) { return false; } - msg.current_param.param_type = *val; - buffer.pop(); + + msg.current_param = {}; + msg.current_param.param_type = type; } // decode param_len: @@ -1052,10 +1056,14 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& } /// num params - buffer.push(qtransport::to_uintV(static_cast(1))); + buffer.push(qtransport::to_uintV(static_cast(2))); // role param buffer.push(qtransport::to_uintV(static_cast(msg.role_parameter.param_type))); buffer.push_lv(msg.role_parameter.param_value); + // endpoint_id param + buffer.push(qtransport::to_uintV(static_cast(ParameterType::EndpointId))); + buffer.push_lv(msg.endpoint_id_parameter.param_value); + return buffer; } @@ -1089,26 +1097,39 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqClientSetup &msg) msg.num_params = params; } while (msg.num_params > 0) { - if (!msg.current_param.has_value()) { - auto val = buffer.front(); - if (!val) { - return false; + if (!msg.current_param.has_value()) { + uint64_t type{ 0 }; + if (!parse_uintV_field(buffer, type)) { + return false; + } + + msg.current_param = MoqParameter{}; + msg.current_param->param_type = type; } - msg.current_param = MoqParameter{}; - msg.current_param->param_type = *val; - buffer.pop(); - } - auto param = buffer.decode_bytes(); - if (!param) { - return false; - } - msg.current_param->param_length = param->size(); - msg.current_param->param_value = param.value(); - static_cast(msg.current_param->param_type) == ParameterType::Role ? - msg.role_parameter = std::move(msg.current_param.value()) : msg.path_parameter = std::move(msg.current_param.value()); - msg.current_param = MoqParameter{}; - msg.num_params.value() -= 1; + auto param = buffer.decode_bytes(); + if (!param) { + return false; + } + msg.current_param->param_length = param->size(); + msg.current_param->param_value = param.value(); + + switch (static_cast(msg.current_param->param_type)) { + case ParameterType::Role: + msg.role_parameter = std::move(msg.current_param.value()); + break; + case ParameterType::Path: + msg.path_parameter = std::move(msg.current_param.value()); + break; + case ParameterType::EndpointId: + msg.endpoint_id_parameter = std::move(msg.current_param.value()); + break; + default: + break; + } + + msg.current_param = std::nullopt; + msg.num_params.value() -= 1; } msg.parse_completed = true; @@ -1136,10 +1157,15 @@ qtransport::StreamBuffer& operator<<(qtransport::StreamBuffer& buffer.push(qtransport::to_uintV(msg.selection_version)); /// num params - buffer.push(qtransport::to_uintV(static_cast(1))); + buffer.push(qtransport::to_uintV(static_cast(2))); // role param buffer.push(qtransport::to_uintV(static_cast(msg.role_parameter.param_type))); buffer.push_lv(msg.role_parameter.param_value); + + // endpoint_id param + buffer.push(qtransport::to_uintV(static_cast(ParameterType::EndpointId))); + buffer.push_lv(msg.endpoint_id_parameter.param_value); + return buffer; } @@ -1162,13 +1188,13 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) } while (msg.num_params > 0) { if (!msg.current_param.has_value()) { - auto val = buffer.front(); - if (!val) { - return false; + uint64_t type {0}; + if (!parse_uintV_field(buffer, type)) { + return false; } + msg.current_param = MoqParameter{}; - msg.current_param->param_type = *val; - buffer.pop(); + msg.current_param->param_type = type; } auto param = buffer.decode_bytes(); @@ -1177,10 +1203,22 @@ bool operator>>(qtransport::StreamBuffer &buffer, MoqServerSetup &msg) } msg.current_param->param_length = param->size(); msg.current_param->param_value = param.value(); - static_cast(msg.current_param->param_type) == ParameterType::Role - ? msg.role_parameter = std::move(msg.current_param.value()) - : msg.path_parameter = std::move(msg.current_param.value()); - msg.current_param = MoqParameter{}; + + switch (static_cast(msg.current_param->param_type)) { + case ParameterType::Role: + msg.role_parameter = std::move(msg.current_param.value()); + break; + case ParameterType::Path: + msg.path_parameter = std::move(msg.current_param.value()); + break; + case ParameterType::EndpointId: + msg.endpoint_id_parameter = std::move(msg.current_param.value()); + break; + default: + break; + } + + msg.current_param = std::nullopt; msg.num_params.value() -= 1; } msg.parse_completed = true; diff --git a/test/fake_transport.h b/test/fake_transport.h index 25347483..a9c7bf89 100644 --- a/test/fake_transport.h +++ b/test/fake_transport.h @@ -60,7 +60,7 @@ struct FakeTransport : public ITransport [[maybe_unused]] uint8_t priority) override {} std::shared_ptr> getStreamBuffer(TransportConnId, uint64_t) override { return nullptr;} - void close(const TransportConnId& /* conn_id */) override {}; + void close(const TransportConnId& /* conn_id */, uint64_t) override {}; void deleteDataContext([[maybe_unused]] const TransportConnId& conn_id, [[maybe_unused]] DataContextId data_ctx_id) override diff --git a/test/moq_messages.cpp b/test/moq_messages.cpp index 85583f3d..51bcc42f 100644 --- a/test/moq_messages.cpp +++ b/test/moq_messages.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace quicr; using namespace quicr::messages; @@ -528,12 +529,14 @@ TEST_CASE("SubscribeDone (content-exists) Message encode/decode") TEST_CASE("ClientSetup Message encode/decode") { qtransport::StreamBuffer buffer; + const std::string endpoint_id = "client test"; auto client_setup = MoqClientSetup {}; client_setup.num_versions = 2; client_setup.supported_versions = {0x1000, 0x2000}; client_setup.role_parameter.param_type = static_cast(ParameterType::Role); client_setup.role_parameter.param_length = 0x1; client_setup.role_parameter.param_value = {0xFF}; + client_setup.endpoint_id_parameter.param_value.assign(endpoint_id.begin(), endpoint_id.end()); buffer << client_setup; @@ -543,17 +546,20 @@ TEST_CASE("ClientSetup Message encode/decode") CHECK(verify(net_data, static_cast(MoQMessageType::CLIENT_SETUP), client_setup_out)); CHECK_EQ(client_setup.supported_versions, client_setup_out.supported_versions); CHECK_EQ(client_setup.role_parameter.param_value, client_setup_out.role_parameter.param_value); + CHECK_EQ(client_setup.endpoint_id_parameter.param_value, client_setup_out.endpoint_id_parameter.param_value); } TEST_CASE("ServerSetup Message encode/decode") { qtransport::StreamBuffer buffer; + const std::string endpoint_id = "server_test"; auto server_setup = MoqServerSetup {}; server_setup.selection_version = {0x1000}; server_setup.role_parameter.param_type = static_cast(ParameterType::Role); server_setup.role_parameter.param_length = 0x1; server_setup.role_parameter.param_value = {0xFF}; + server_setup.endpoint_id_parameter.param_value.assign(endpoint_id.begin(), endpoint_id.end()); buffer << server_setup; @@ -563,6 +569,7 @@ TEST_CASE("ServerSetup Message encode/decode") CHECK(verify(net_data, static_cast(MoQMessageType::SERVER_SETUP), server_setup_out)); CHECK_EQ(server_setup.selection_version, server_setup_out.selection_version); CHECK_EQ(server_setup.role_parameter.param_value, server_setup.role_parameter.param_value); + CHECK_EQ(server_setup.endpoint_id_parameter.param_value, server_setup_out.endpoint_id_parameter.param_value); } TEST_CASE("ObjectStream Message encode/decode") From d133cf06f34aa8f8cdf0eb3d24a86bee21454b89 Mon Sep 17 00:00:00 2001 From: Tim Evens Date: Tue, 11 Jun 2024 22:34:25 -0700 Subject: [PATCH 36/71] Add cxxopts for cli parsing --- cmd/moq-example/client.cpp | 69 +- cmd/moq-example/server.cpp | 46 +- include/oss/cxxopts.hpp | 2908 ++++++++++++++++++++++++++++++++++++ 3 files changed, 2994 insertions(+), 29 deletions(-) create mode 100644 include/oss/cxxopts.hpp diff --git a/cmd/moq-example/client.cpp b/cmd/moq-example/client.cpp index 094d98ff..189d05da 100644 --- a/cmd/moq-example/client.cpp +++ b/cmd/moq-example/client.cpp @@ -3,9 +3,9 @@ #include "signal_handler.h" -#include #include -#include +#include + class clientDelegate : public quicr::MoQInstanceDelegate { @@ -23,27 +23,47 @@ main(int argc, char* argv[]) int result_code = EXIT_SUCCESS; cantina::LoggerPointer logger = std::make_shared("qclient"); - logger->SetLogLevel("DEBUG"); - - if ((argc != 2) && (argc != 3)) { - std::cerr << "Relay address and port set in MOQ_RELAY and MOQ_PORT env variables." << std::endl; - std::cerr << std::endl; - std::cerr << "Usage publisher: qclient pub " << std::endl; - std::cerr << "Usage subscriber: qclient " << std::endl; - exit(-1); // NOLINT(concurrency-mt-unsafe) + + cxxopts::Options options("qclient", "MOQ Example Client"); + options + .set_width(75) + .set_tab_expansion() + .allow_unrecognised_options() + .add_options() + ("h,help", "Print help") + ("d,debug", "Enable debugging") // a bool parameter + ("r,host", "Relay host/IP", cxxopts::value()->default_value("localhost")) + ("p,port", "Relay port", cxxopts::value()->default_value("1234")) + ("e,endpoint_id", "This client endpoint ID", cxxopts::value()->default_value("moq-client")) + ("q,qlog", "Enable qlog using path", cxxopts::value()) + ; // end of options + + options.add_options("Publisher") + ("pn,pub_namespace", "Track namespace", cxxopts::value()->default_value("client1")) + ("pt,pub_name", "Track name", cxxopts::value>()->default_value("chat")) + ; + + options.add_options("Subscriber") + ("sn,sub_namespace", "Track namespace", cxxopts::value()->default_value("client1")) + ("st,sub_name", "Track name", cxxopts::value>()->default_value("chat")) + ; + + auto result = options.parse(argc, argv); + + if (result.count("help")) + { + std::cout << options.help({"", "Publisher", "Subscriber"}) << std::endl; + return true; } - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto* relayName = getenv("MOQ_RELAY"); - if (relayName == nullptr) { - relayName = "127.0.0.1"; + std::string qlog_path; + if (result.count("qlog")) { + qlog_path = result["qlog"].as(); } - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto* portVar = getenv("MOQ_PORT"); - int port = 1234; - if (portVar != nullptr) { - port = atoi(portVar); // NOLINT(cert-err34-c) + if (result.count("debug") && result["debug"].as() == true) { + logger->info << "setting debug level" << std::flush; + logger->SetLogLevel("DEBUG"); } // Install a signal handlers to catch operating system signals @@ -53,13 +73,16 @@ main(int argc, char* argv[]) std::unique_lock lock(moq_example::main_mutex); quicr::MoQInstanceClientConfig config; - config.endpoint_id = "moq_client"; - config.server_host_ip = "127.0.0.1"; - config.server_port = 1234; + config.endpoint_id = result["endpoint_id"].as(); + config.server_host_ip = result["host"].as(); + config.server_port = result["port"].as(); config.server_proto = qtransport::TransportProtocol::QUIC; - config.transport_config.debug = true; + config.transport_config.debug = result["debug"].as();; config.transport_config.use_reset_wait_strategy = false; config.transport_config.time_queue_max_duration = 5000; + config.transport_config.tls_cert_filename = nullptr; + config.transport_config.tls_key_filename = nullptr; + config.transport_config.quic_qlog_path = qlog_path.size() ? const_cast(qlog_path.c_str()) : nullptr; auto delegate = std::make_shared(); diff --git a/cmd/moq-example/server.cpp b/cmd/moq-example/server.cpp index ee189862..6ebc1604 100644 --- a/cmd/moq-example/server.cpp +++ b/cmd/moq-example/server.cpp @@ -4,6 +4,8 @@ #include #include #include +#include + #include "signal_handler.h" @@ -20,12 +22,43 @@ class serverDelegate : public quicr::MoQInstanceDelegate }; int -main() +main(int argc, char* argv[]) { int result_code = EXIT_SUCCESS; auto logger = std::make_shared("qserver"); - logger->SetLogLevel("DEBUG"); + + cxxopts::Options options("qclient", "MOQ Example Client"); + options + .set_width(75) + .set_tab_expansion() + .allow_unrecognised_options() + .add_options() + ("h,help", "Print help") + ("d,debug", "Enable debugging") // a bool parameter + ("b,bind_ip", "Bind IP", cxxopts::value()->default_value("127.0.0.1")) + ("p,port", "Listening port", cxxopts::value()->default_value("1234")) + ("e,endpoint_id", "This relay/server endpoint ID", cxxopts::value()->default_value("moq-server")) + ("q,qlog", "Enable qlog using path", cxxopts::value()) + ; // end of options + + auto result = options.parse(argc, argv); + + if (result.count("help")) + { + std::cout << options.help({""}) << std::endl; + return true; + } + + std::string qlog_path; + if (result.count("qlog")) { + qlog_path = result["qlog"].as(); + } + + if (result.count("debug") && result["debug"].as() == true) { + logger->info << "setting debug level" << std::flush; + logger->SetLogLevel("DEBUG"); + } // Install a signal handlers to catch operating system signals installSignalHandlers(); @@ -34,15 +67,16 @@ main() std::unique_lock lock(moq_example::main_mutex); quicr::MoQInstanceServerConfig config; - config.endpoint_id = "moq_server"; - config.server_bind_ip = "127.0.0.1"; - config.server_port = 1234; + config.endpoint_id = result["endpoint_id"].as(); + config.server_bind_ip = result["bind_ip"].as(); + config.server_port = result["port"].as(); config.server_proto = qtransport::TransportProtocol::QUIC; - config.transport_config.debug = true; + config.transport_config.debug = result["debug"].as(); config.transport_config.tls_cert_filename = "./server-cert.pem"; config.transport_config.tls_key_filename = "./server-key.pem"; config.transport_config.use_reset_wait_strategy = false; config.transport_config.time_queue_max_duration = 5000; + config.transport_config.quic_qlog_path = qlog_path.size() ? const_cast(qlog_path.c_str()) : nullptr; auto delegate = std::make_shared(); diff --git a/include/oss/cxxopts.hpp b/include/oss/cxxopts.hpp new file mode 100644 index 00000000..ff163e54 --- /dev/null +++ b/include/oss/cxxopts.hpp @@ -0,0 +1,2908 @@ +/* + +Copyright (c) 2014-2022 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// vim: ts=2:sw=2:expandtab + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH +}; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts { + +using String = icu::UnicodeString; + +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator +{ + public: + + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); +} + +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) +{ + for (std::size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; +} + +inline +size_t +stringLength(const String& s) +{ + return static_cast(s.length()); +} + +inline +std::string +toUTF8String(const String& s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +inline +bool +empty(const String& s) +{ + return s.isEmpty(); +} + +} // namespace cxxopts + +namespace std { + +inline +cxxopts::UnicodeStringIterator +begin(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, 0); +} + +inline +cxxopts::UnicodeStringIterator +end(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, s.length()); +} + +} // namespace std + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts { + +using String = std::string; + +template +T +toLocalString(T&& t) +{ + return std::forward(t); +} + +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} + +inline +String& +stringAppend(String&s, const String& a) +{ + return s.append(a); +} + +inline +String& +stringAppend(String& s, std::size_t n, char c) +{ + return s.append(n, c); +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template +std::string +toUTF8String(T&& t) +{ + return std::forward(t); +} + +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} + +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this +{ + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + add(const std::string& text) const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; +}; + +class specification : public exception +{ + public: + + explicit specification(const std::string& message) + : exception(message) + { + } +}; + +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) + { + } +}; + +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } +}; + +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) + { + } +}; + +class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string& text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } +}; + +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } +}; + +class missing_argument : public parsing +{ + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } +}; + +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } +}; + +class gratuitous_argument_for_option : public parsing +{ + public: + gratuitous_argument_for_option + ( + const std::string& option, + const std::string& arg + ) + : parsing( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } +}; + +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; + +class option_has_no_value : public exception +{ + public: + explicit option_has_no_value(const std::string& option) + : exception( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } +}; + +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; + +} // namespace exceptions + + +template +void throw_or_mimic(const std::string& text) +{ + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} + +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) + { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char* option_name_valid_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} + +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; +CXXOPTS_LINKONCE +const char* const truthy_pattern = + "(t|T)(rue)?|1"; +CXXOPTS_LINKONCE +const char* const falsy_pattern = + "(f|F)(alse)?|0"; +CXXOPTS_LINKONCE +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; +CXXOPTS_LINKONCE +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; +CXXOPTS_LINKONCE +const char* const option_specifier_separator_pattern = ", *"; + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + static const std::basic_regex integer_matcher(integer_pattern); + + std::smatch match; + std::regex_match(text, match, integer_matcher); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + static const std::basic_regex truthy_matcher(truthy_pattern); + std::smatch result; + std::regex_match(text, result, truthy_matcher); + return !result.empty(); +} + +inline bool IsFalseText(const std::string &text) +{ + static const std::basic_regex falsy_matcher(falsy_pattern); + std::smatch result; + std::regex_match(text, result, falsy_matcher); + return !result.empty(); +} + +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) +{ + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) + { + throw_or_mimic(text); + } + + OptionNames split_names; + + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + static const std::basic_regex option_matcher(option_pattern); + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; +} + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template +struct SignedCheck; + +template +struct SignedCheck +{ + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } +}; + +template +struct SignedCheck +{ + template + void + operator()(bool, U, const std::string&) const {} +}; + +template +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck::is_signed>()(negative, value, text); +} + +} // namespace detail + +template +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); +} + +template +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic(text); +} + +template +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast((std::numeric_limits::min)()))); + } + else + { + limit = (std::numeric_limits::max)(); + } + + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) + { + throw_or_mimic(text); + } + + result = static_cast(result * base + digit); + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } +} + +template +void stringstream_parser(const std::string& text, T& value) +{ + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } +} + +template ::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} + +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic(text); +} + +inline +void +parse_value(const std::string& text, std::string& value) +{ + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template ::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + +template +void +parse_value(const std::string& text, std::vector& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +template +void +add_value(const std::string& text, T& value) +{ + parse_value(text, value); +} + +template +void +add_value(const std::string& text, std::vector& value) +{ + T v; + add_value(text, v); + value.emplace_back(std::move(v)); +} + +template +struct type_is_container +{ + static constexpr bool value = false; +}; + +template +struct type_is_container> +{ + static constexpr bool value = true; +}; + +template +class abstract_value : public Value +{ + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + add(const std::string& text) const override + { + add_value(text, *m_store); + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; +}; + +template +class standard_value : public abstract_value +{ + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } +}; + +template <> +class standard_value : public abstract_value +{ + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; + +} // namespace values + +template +std::shared_ptr +value() +{ + return std::make_shared>(); +} + +template +std::shared_ptr +value(T& t) +{ + return std::make_shared>(&t); +} + +class OptionAdder; + +CXXOPTS_NODISCARD +inline +const std::string& +first_or_empty(const OptionNames& long_names) +{ + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} + +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(first_long_name() + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + first_long_name() const + { + return first_or_empty(m_long); + } + + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } + + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } + + std::size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + std::size_t m_hash{}; +}; + +struct HelpOptionDetails +{ + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails +{ + std::string name{}; + std::string description{}; + std::vector options{}; +}; + +class OptionValue +{ + public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& details) + { + m_long_names = &details->long_names(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif + + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); + } + + return CXXOPTS_RTTI_CAST&>(*m_value).get(); + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional() const + { + if (m_value == nullptr) { + return std::nullopt; + } + return as(); + } +#endif + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const OptionNames * m_long_names = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue +{ + public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; + +class ParseResult +{ + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; + + Iterator() = default; + Iterator(const Iterator&) = default; + +// GCC complains about m_iter not being initialised in the member +// initializer list +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) + { + if (end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } +CXXOPTS_DIAGNOSTIC_POP + + Iterator& operator++() + { + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator& other) const + { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator& other) const + { + return !(*this == other); + } + + const KeyValue& operator*() + { + return *m_iter; + } + + const KeyValue* operator->() + { + return m_iter.operator->(); + } + + private: + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + Iterator + begin() const + { + return Iterator(this); + } + + Iterator + end() const + { + return Iterator(this, true); + } + + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional + as_optional(const std::string& option) const + { + auto iter = m_keys.find(option); + if (iter != m_keys.end()) + { + auto viter = m_values.find(iter->second); + if (viter != m_values.end()) + { + return viter->second.as_optional(); + } + } + return std::nullopt; + } +#endif + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + const std::vector& + defaults() const + { + return m_defaults; + } + + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; + +struct Option +{ + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(const std::shared_ptr& value, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options +{ + public: + + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list