├── .clang-format ├── .clang-tidy ├── .git-blame-ignore-revs ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── AUTHORS.md ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── LICENSES ├── BSD-2-Clause.txt ├── CC-BY-4.0.txt └── MIT.txt ├── Makefile ├── README.md ├── REUSE.toml ├── benchmark ├── .clang-tidy ├── CMakeLists.txt ├── data_storage.cpp ├── hash.cpp ├── stream_buffer.cpp ├── time_queue.cpp └── uintvar.cpp ├── build.Dockerfile ├── certs ├── LICENSE ├── badcert.pem ├── cert.pem ├── esni-rr.bin ├── esni-secp256r1.key ├── key.pem └── test-ca.crt ├── cmake ├── FindMbedTLS.cmake └── Lint.cmake ├── cmd ├── CMakeLists.txt ├── dependencies │ └── oss │ │ └── cxxopts.hpp ├── examples │ ├── CMakeLists.txt │ ├── client.cpp │ ├── helper_functions.h │ ├── server.cpp │ └── signal_handler.h ├── qperf │ ├── CMakeLists.txt │ └── qperf.cpp └── qperf2 │ ├── CMakeLists.txt │ ├── config.ini │ ├── inicpp.h │ ├── qperf.hpp │ ├── qperf_pub.cc │ ├── qperf_pub.hpp │ ├── qperf_sub.cc │ ├── qperf_sub.hpp │ └── run_parallel.sh ├── dependencies └── CMakeLists.txt ├── docs ├── LICENSE ├── api-guide.md ├── api-main.md ├── css │ ├── doxygen-awesome-sidebar-only.css │ └── doxygen-awesome.css ├── images │ └── client-api.png ├── implementation.md └── pandoc-theme │ └── elegant_bootstrap_menu.html ├── include └── quicr │ ├── cache.h │ ├── client.h │ ├── common.h │ ├── config.h │ ├── detail │ ├── base_track_handler.h │ ├── ctrl_message_types.h │ ├── ctrl_messages.h │ ├── data_storage.h │ ├── defer.h │ ├── joining_fetch_handler.h │ ├── messages.h │ ├── priority_queue.h │ ├── quic_transport.h │ ├── quic_transport_metrics.h │ ├── safe_queue.h │ ├── stream_buffer.h │ ├── tick_service.h │ ├── time_queue.h │ ├── transport.h │ └── uintvar.h │ ├── fetch_track_handler.h │ ├── hash.h │ ├── metrics.h │ ├── object.h │ ├── publish_fetch_handler.h │ ├── publish_track_handler.h │ ├── server.h │ ├── subscribe_track_handler.h │ └── track_name.h ├── src ├── CMakeLists.txt ├── client.cpp ├── ctrl_message_types.cpp ├── ctrl_messages.cpp ├── fetch_track_handler.cpp ├── joining_fetch_handler.cpp ├── messages.cpp ├── publish_fetch_handler.cpp ├── publish_track_handler.cpp ├── quic_transport.cpp ├── server.cpp ├── subscribe_track_handler.cpp ├── transport.cpp ├── transport_picoquic.cpp ├── transport_picoquic.h └── version.h.in ├── test ├── CMakeLists.txt ├── cache.cpp ├── client.cpp ├── data_storage.cpp ├── main.cpp ├── moq_ctrl_messages.cpp ├── moq_data_messages.cpp ├── moq_test.cpp ├── tick_service.cpp ├── track_handlers.cpp ├── track_namespace.cpp └── uintvar.cpp └── tools └── draft_parser ├── README.md ├── ctrl_messages.cpp ├── ctrl_messages.h ├── drafts ├── moq_transport_draft_v10.txt ├── moq_transport_draft_v10_with_addendum.txt ├── moq_transport_draft_v11.txt ├── moq_transport_draft_v11_with_addendum.txt ├── moq_transport_draft_v8.txt ├── moq_transport_draft_v8_with_addendum.txt └── new_group_addendum.txt ├── main.py ├── moqt_parser ├── __init__.py ├── code_generator.py ├── message_spec.py ├── parsers.py ├── table_parser.py └── templates │ └── cpp │ ├── MessageSpec_Header_Begin_tmpl.jinja2 │ ├── MessageSpec_Header_End_tmpl.jinja2 │ ├── MessageSpec_Header_Enums_tmpl.jinja2 │ ├── MessageSpec_Header_Structs_tmpl.jinja2 │ ├── MessageSpec_Header_Using_tmpl.jinja2 │ ├── MessageSpec_Source_Begin_tmpl.jinja2 │ ├── MessageSpec_Source_End_tmpl.jinja2 │ ├── MessageSpec_Source_Structs_tmple.jinja2 │ ├── MessageSpec_Source_Using_templ.jinja2 │ ├── Transport_Header_Begin_tmpl.jinja2 │ ├── Transport_Header_End_tmpl.jinja2 │ ├── Transport_Header_Structs_tmpl.jinja2 │ ├── Transport_Source_Begin_tmpl.jinja2 │ ├── Transport_Source_End_tmpl.jinja2 │ └── Transport_Source_Structs_tmpl.jinja2 └── requirements.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Mozilla 4 | 5 | BreakAfterAttributes: Leave 6 | ColumnLimit: 120 7 | IndentWidth: 4 8 | NamespaceIndentation: All 9 | BraceWrapping: 10 | AfterFunction: false 11 | ... 12 | 13 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: ' 3 | -*, 4 | -clang-analyzer-*, 5 | -misc-include-cleaner, 6 | -misc-misplaced-const, 7 | -performance-*, 8 | -readability-avoid-const-params-in-decls, 9 | -readability-const-return-type, 10 | readability-identifier-naming, 11 | -readability-make-member-function-const, 12 | -readability-non-const-parameter, 13 | ' 14 | CheckOptions: 15 | - { key: readability-identifier-naming.ClassCase, value: CamelCase } 16 | - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } 17 | - { key: readability-identifier-naming.EnumCase, value: CamelCase } 18 | - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } 19 | - { key: readability-identifier-naming.EnumConstantPrefix, value: k } 20 | - { key: readability-identifier-naming.FunctionCase, value: CamelCase } 21 | - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } 22 | - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } 23 | - { key: readability-identifier-naming.LocalConstantCase, value: lower_case } 24 | - { key: readability-identifier-naming.LocalConstantPrefix, value: '' } 25 | - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } 26 | - { key: readability-identifier-naming.StaticConstantPrefix, value: k } 27 | - { key: readability-identifier-naming.StaticVariableCase, value: lower_case } 28 | - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } 29 | - { key: readability-identifier-naming.MacroDefinitionIgnoredRegexp, value: '^[A-Z]+(_[A-Z]+)*_$' } 30 | - { key: readability-identifier-naming.MemberCase, value: lower_case } 31 | - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } 32 | - { key: readability-identifier-naming.PublicMemberSuffix, value: '' } 33 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 34 | - { key: readability-identifier-naming.ParameterCase, value: lower_case } 35 | - { key: readability-identifier-naming.TypeAliasCase, value: CamelCase } 36 | - { key: readability-identifier-naming.TypedefCase, value: CamelCase } 37 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 38 | - { key: readability-identifier-naming.IgnoreMainLikeFunctions, value: 1 } 39 | WarningsAsErrors: "*" 40 | HeaderFilterRegex: "(/include/transport|/src)/.*" 41 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 8148022c949c32eb139caf996d4729a2f1aecc64 2 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | format-check: 15 | name: Format Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: jidicula/clang-format-action@v4.13.0 20 | with: 21 | clang-format-version: '18' 22 | exclude-regex: '(dependencies)' 23 | 24 | build: 25 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 26 | # You can convert this to a matrix build if you need cross-platform coverage. 27 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 28 | strategy: 29 | matrix: 30 | os: [ macos-latest, ubuntu-latest ] 31 | 32 | runs-on: ${{ matrix.os }} 33 | continue-on-error: true 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | with: 38 | submodules: recursive 39 | 40 | - name: Install Packages (macOS) 41 | run: | 42 | brew install llvm go 43 | ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" 44 | brew install google-benchmark 45 | if: matrix.os == 'macos-latest' 46 | 47 | - name: Install Packages (Ubuntu) 48 | run: | 49 | sudo apt-get install -y clang-tidy 50 | if: matrix.os == 'ubuntu-latest' 51 | 52 | - name: Configure CMake 53 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 54 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 55 | run: cmake -B ${{github.workspace}}/build -DLINT=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTING=ON -DBUILD_BENCHMARKING=ON 56 | 57 | - name: Build 58 | # Build your program with the given configuration 59 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j $(getconf _NPROCESSORS_ONLN) 60 | 61 | - name: Test 62 | working-directory: ${{github.workspace}}/build 63 | # Execute tests defined by the CMake configuration. 64 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 65 | run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure 66 | 67 | - name: Benchmark 68 | run: ${{github.workspace}}/build/benchmark/quicr_benchmark 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | *.d 7 | *.tmp 8 | 9 | # Precompiled Headers 10 | *.gch 11 | *.pch 12 | 13 | # Compiled Dynamic libraries 14 | *.so 15 | *.dylib 16 | *.dll 17 | 18 | # Compiled Static libraries 19 | *.lai 20 | *.la 21 | *.a 22 | *.lib 23 | 24 | # Executables 25 | *.exe 26 | *.out 27 | *.app 28 | 29 | # Temp Files 30 | .DS_Store 31 | *.swp 32 | *~ 33 | /build 34 | 35 | # log files 36 | *.log 37 | 38 | # IDEA 39 | .idea 40 | cmake-build-debug 41 | cmake-build-release 42 | 43 | # Editor files/directories 44 | /.vscode 45 | /.vs 46 | /CMakeSettings.json 47 | .projections.json 48 | 49 | # vcpkg 50 | /vcpkg_installed 51 | 52 | # Other 53 | .tmp.dprobes.h 54 | libquicr.spdx 55 | 56 | # Python 57 | .venv 58 | .vscode/ 59 | __pycache__/ 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/doctest"] 2 | path = dependencies/doctest 3 | url = https://github.com/doctest/doctest.git 4 | [submodule "dependencies/picotls"] 5 | path = dependencies/picotls 6 | url = https://github.com/quicr/picotls 7 | [submodule "dependencies/picoquic"] 8 | path = dependencies/picoquic 9 | url = https://github.com/quicr/picoquic 10 | [submodule "dependencies/mbedtls"] 11 | path = dependencies/mbedtls 12 | url = https://github.com/Mbed-TLS/mbedtls.git 13 | [submodule "dependencies/spdlog"] 14 | path = dependencies/spdlog 15 | url = https://github.com/gabime/spdlog.git 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-clang-format 3 | rev: v18.1.8 4 | hooks: 5 | - id: clang-format 6 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | ### Contributors 2 | 3 | The following are significant contributors to this repository: 4 | 5 | * [Richard Barnes](https://github.com/bifurcation) 6 | * [Cullen Jennings](https://github.com/fluffy) 7 | * [Tomas Rigaux](https://github.com/GhostofCookie) 8 | * [Paul E. Jones](https://github.com/paulej) 9 | * [Rich Logan](https://github.com/RichLogan) 10 | * [Scott Henning](https://github.com/shenning00) 11 | * [Suhas Nandakumar](https://github.com/suhasHere) 12 | * [Tim Evens](https://github.com/TimEvens) 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | cmake_minimum_required(VERSION 3.13) 5 | 6 | # Build tests by default only if not a sub-project 7 | if(DEFINED PROJECT_NAME) 8 | option(QUICR_BUILD_TESTS "Build tests for quicr" OFF) 9 | option(quicr_BUILD_BENCHMARKS "Build benchmarks for quicr" OFF) 10 | else() 11 | option(QUICR_BUILD_TESTS "Build tests for quicr" ON) 12 | option(quicr_BUILD_BENCHMARKS "Build benchmarks for quicr" ON) 13 | endif() 14 | 15 | project(quicr 16 | VERSION 1.0.2 17 | DESCRIPTION "QuicR, a Media over QUIC Library" 18 | LANGUAGES CXX) 19 | 20 | if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") 21 | message(FATAL_ERROR 22 | "In-source builds are disallowed, prefer libquicr's Makefile or cmake -B -S 23 | You may need to delete the already generated Cache and CMake folder to continue.") 24 | endif() 25 | 26 | configure_file( src/version.h.in ${CMAKE_BINARY_DIR}/include/quicr/version.h ) 27 | 28 | IF (NOT UNIX AND NOT APPLE AND NOT PLATFORM_ESP_IDF) 29 | message(FATAL_ERROR "Unsupported platform, Linux and Apple are the only supported platforms") 30 | endif () 31 | 32 | set (supported_archs arm64 x86_64 aarch64) 33 | if (NOT ${CMAKE_SYSTEM_PROCESSOR} IN_LIST supported_archs) 34 | message(FATAL_ERROR "Unsupported system architecture '${CMAKE_SYSTEM_PROCESSOR}'. Supported is arm64 and x86_64") 35 | endif() 36 | 37 | message(STATUS "Building for ${CMAKE_SYSTEM_PROCESSOR}") 38 | 39 | option(LINT "Perform linting with clang-tidy" OFF) 40 | option(QUICR_BUILD_SHARED "Build quicr as a SHARED library" OFF) 41 | option(PLATFORM_ESP_IDF "Enabble support for esp-idf (Default OFF)" OFF) 42 | option(USE_MBEDTLS OFF) 43 | 44 | if (NOT PLATFORM_ESP_IDF) 45 | find_package(Threads REQUIRED) 46 | find_package(PkgConfig REQUIRED) 47 | else() 48 | set(USE_MBEDTLS ON) 49 | endif() 50 | 51 | ### 52 | ### Global Config 53 | ### 54 | set (BUILD_SHARED_LIBS OFF) 55 | set (BUILD_STATIC_LIBS ON) 56 | 57 | if (NOT PLATFORM_ESP_IDF) 58 | set (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 59 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dependencies/transport/cmake) 60 | endif() 61 | 62 | ### 63 | ### Include Ctest to ensure BUILD_TESTING is set 64 | ### 65 | include(CTest) 66 | 67 | ### 68 | ### Dependencies 69 | ### 70 | add_subdirectory(dependencies) 71 | 72 | ### 73 | ### Build the quicr library 74 | ### 75 | add_subdirectory(src) 76 | 77 | ### 78 | ### Enable testing and add tests if QUICR_BUILD_TESTS is ON 79 | ### 80 | if(BUILD_TESTING AND QUICR_BUILD_TESTS) 81 | add_subdirectory(test) 82 | endif() 83 | 84 | if (BUILD_BENCHMARKING AND quicr_BUILD_BENCHMARKS) 85 | add_subdirectory(benchmark) 86 | endif() 87 | 88 | ### 89 | ### Applications 90 | ### 91 | if(BUILD_TESTING AND QUICR_BUILD_TESTS) 92 | add_subdirectory(cmd) 93 | endif() 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Quicr 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /LICENSES/BSD-2-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # This is just a convenience Makefile to avoid having to remember 5 | # all the CMake commands and their arguments. 6 | 7 | # Set CMAKE_GENERATOR in the environment to select how you build, e.g.: 8 | # CMAKE_GENERATOR=Ninja 9 | 10 | BUILD_DIR=build 11 | export MERMAID_FILTER_THEME=neutral 12 | CLANG_FORMAT=clang-format -i 13 | 14 | .PHONY: all clean cclean format tidy 15 | 16 | all: ${BUILD_DIR} 17 | cmake --build ${BUILD_DIR} --parallel 8 18 | 19 | ${BUILD_DIR}: CMakeLists.txt cmd/CMakeLists.txt 20 | cmake -B${BUILD_DIR} -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DBUILD_TESTING=TRUE -DQUICR_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_MBEDTLS=OFF . 21 | 22 | tidy: CMakeLists.txt cmd/CMakeLists.txt 23 | cmake -B${BUILD_DIR} -DBUILD_TESTING=TRUE -DQUICR_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DLINT=ON . 24 | 25 | ci: CMakeLists.txt cmd/CMakeLists.txt 26 | cmake -B${BUILD_DIR} -DLINT=ON -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DBUILD_BENCHMARKING=ON 27 | 28 | cert: 29 | @echo "Creating certificate in ${BUILD_DIR}/cmd/examples" 30 | @openssl req -nodes -x509 -newkey rsa:2048 -days 365 \ 31 | -subj "/C=US/ST=CA/L=San Jose/O=Cisco/CN=test.m10x.org" \ 32 | -keyout ${BUILD_DIR}/cmd/examples/server-key.pem -out ${BUILD_DIR}/cmd/examples/server-cert.pem 33 | test: ci 34 | cmake --build ${BUILD_DIR} 35 | ctest --test-dir ${BUILD_DIR} --output-on-failure 36 | 37 | clean: 38 | cmake --build ${BUILD_DIR} --target clean 39 | 40 | cclean: 41 | rm -rf ${BUILD_DIR} 42 | 43 | doc: 44 | @echo "Creating Doxygen Docs" 45 | @doxygen 46 | @echo "Creating implementation doc HTML" 47 | @pandoc docs/api-guide.md -f markdown --to=html5 -o docs/html/api-guide.html --filter=mermaid-filter \ 48 | --template=docs/pandoc-theme/elegant_bootstrap_menu.html --toc 49 | 50 | format: 51 | find include -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 52 | find src -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 53 | find src/moq -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 54 | find src/quic -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 55 | find test -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 56 | find cmd -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 57 | find benchmark -name "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 58 | find tools -iname "*.h" -or -iname "*.cpp" | xargs ${CLANG_FORMAT} 59 | 60 | lint: 61 | reuse lint 62 | 63 | sbom: 64 | reuse spdx -o libquicr.spdx 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libquicr 2 | ======== 3 | 4 | [![Build](https://github.com/Quicr/libquicr/actions/workflows/cmake.yml/badge.svg?branch=main)](https://github.com/Quicr/libquicr/actions/workflows/cmake.yml) 5 | 6 | An API library that implements publish/subscribe protocol [draft-ietf-moq-transport-04](https://datatracker.ietf.org/doc/html/draft-ietf-moq-transport-04). 7 | The API supports both client and server. Server is intended to be implemented as a relay. 8 | 9 | API documentation can be found under https://quicr.github.io/libquicr 10 | 11 | ## Build 12 | 13 | ### Dependencies 14 | 15 | #### Linux 16 | 17 | ``` 18 | sudo apt-get update && sudo apt-get install -y gcc g++ golang pkgconf cmake make git 19 | ``` 20 | 21 | ### Apple/Mac 22 | 23 | Both Apple Intel and Silicon are supported. 24 | 25 | 26 | #### (1) Install Xcode 27 | 28 | > [!NOTE] 29 | > You **MUST** install xcode from Apple in order to get the base development programs. 30 | 31 | 32 | Open the **App Store** and search for **Xcode** and install. 33 | 34 | #### (2) Install Xcode Command Line Tools 35 | 36 | You can install them via xcode UI or you can install them using `xcode-select --install` from a shell/terminal. 37 | 38 | 39 | #### (3) Install Homebrew 40 | 41 | Install via https://brew.sh instructions. 42 | 43 | #### (4) Install packages via brew 44 | 45 | ``` 46 | brew install cmake clang-format 47 | ``` 48 | 49 | #### (5) Install GoLang 50 | Golang is required for BoringSSL build. Install via https://go.dev/doc/install instructions. 51 | 52 | --- 53 | 54 | ## Example 55 | [cmd/examples](https://github.com/Quicr/libquicr/tree/main/cmd/examples) has an example client and server implementation showing chat and clock 56 | applications. 57 | 58 | Running `make` will build the examples. After running `make`, issue a `make cert` to generate a self-signed certificate 59 | that will be used by `qserver`. 60 | 61 | For the server, the default command line arguments look for the server certificate in the local directory. You can override 62 | that with the options `-c ` and `-k `, or you can run `qserver` from within the examples directory. 63 | 64 | ### qServer 65 | qServer is an example server/relay. It implements the server API to accept connections and relay objects to subscribers from publishers. 66 | 67 | Use `qserver -h` to get help. 68 | 69 | By default, no options are required unless you need to change the defaults. The default listening port is **1234** 70 | 71 | ### qClient 72 | qClient is an example client. It implements the client API to make a connetion to the relay and to act as subscriber, publisher, or both. 73 | The client program will read from `stdin` when in publisher mode to publish data. Alternatively, the client can be configured to publish 74 | a timestamp using the option `--clock`. 75 | 76 | Use `qclient -h` to get help. 77 | 78 | > [!NOTE] 79 | > The **namespace** and **name** for both publish and subscribe can be any string value. The only requirement is 80 | > to publish and subscribe to the same values. 81 | 82 | #### As chat subscriber 83 | 84 | ``` 85 | ./qclient --sub_namespace chat --sub_name general 86 | ``` 87 | 88 | #### As chat publisher 89 | 90 | ``` 91 | ./qclient --pub_namespace chat --pub_name general 92 | ``` 93 | 94 | #### As both chat subscriber and publisher 95 | 96 | ``` 97 | ./qclient --sub_namespace chat --sub_name general --pub_namespace chat --pub_name general 98 | ``` 99 | 100 | #### As clock publisher 101 | 102 | ``` 103 | ./qclient --pub_namespace clock --pub_name second --clock 104 | ``` 105 | 106 | #### As clock subscriber 107 | 108 | ``` 109 | ./qclient --sub_namespace clock --sub_name second 110 | ``` 111 | 112 | --- 113 | 114 | ### CMake 115 | 116 | Generate the cmake build directory: 117 | 118 | ```bash 119 | cmake -B build 120 | ``` 121 | 122 | Available options to append to the above command for building tests, benchmarks, and with linting: 123 | 124 | ``` 125 | -DBUILD_TESTS=ON 126 | -DBUILD_BENCHMARKS=ON 127 | -DLINT=ON 128 | ``` 129 | 130 | To build the project, run: 131 | ```bash 132 | cmake --build build -j 133 | ``` 134 | 135 | ### Make 136 | 137 | Use `make` to build libquicr. 138 | 139 | Use `make test` to run tests. 140 | 141 | Use `make cclean` to clean build files. 142 | 143 | ## Self-signed Certificate 144 | 145 | Server requires a TLS certificate and key file. For development and testing, use a self-signed certificate. Below 146 | are the steps to create a self-signed certificate and private ey. 147 | 148 | ### Create cert under cmd/examples 149 | 150 | ``` 151 | make cert 152 | ``` 153 | 154 | ### OpenSSL/BorningSSL 155 | 156 | ``` 157 | cd build/cmd/examples 158 | openssl req -nodes -x509 -newkey rsa:2048 -days 365 \ 159 | -subj "/C=US/ST=CA/L=San Jose/O=Cisco/CN=test.m10x.org" \ 160 | -keyout server-key.pem -out server-cert.pem 161 | ``` 162 | 163 | ### MbedTLS 164 | 165 | ``` 166 | openssl req -nodes -x509 -newkey ec:<(openssl ecparam -name prime256v1) -days 365 \ 167 | -subj "/C=US/ST=CA/L=San Jose/O=Cisco/CN=test.m10x.org" \ 168 | -keyout server-key.pem -out server-cert.pem 169 | ``` 170 | 171 | OR 172 | 173 | ``` 174 | openssl ecparam -name prime256v1 -genkey -noout -out server-key-ec.pem 175 | openssl req -nodes -x509 -key server-key-ec.pem -days 365 \ 176 | -subj "/C=US/ST=CA/L=San Jose/O=Cisco/CN=test.m10x.org" \ 177 | -keyout server-key.pem -out server-cert.pem 178 | 179 | ``` 180 | 181 | --- 182 | 183 | ## Generate documentation 184 | API documentation can be generated using `make doc` 185 | 186 | Below programs need to be installed. 187 | 188 | Example on MacOS: 189 | 190 | * `brew install doxygen` 191 | * `brew install npm` 192 | * `brew install pandoc` 193 | * `npm install --global @mermaid-js/mermaid-cli` 194 | * `npm install --global mermaid-filter` 195 | 196 | > [!NOTE] 197 | > https://github.com/raghur/mermaid-filter adds mermaid support to pandoc 198 | > 199 | 200 | ### MOQ Implementation Documentation 201 | 202 | See [MOQ Implementation](docs/implementation) 203 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | version = 1 5 | 6 | [[annotations]] 7 | path = "cmd/dependencies/oss/cxxopts.hpp" 8 | precedence = "aggregate" 9 | SPDX-FileCopyrightText = "Copyright (c) 2014-2022 Jarryd Beck" 10 | SPDX-License-Identifier = "MIT" 11 | 12 | [[annotations]] 13 | path = "docs/**" 14 | precedence = "aggregate" 15 | SPDX-FileCopyrightText = "Copyright (c) 2024 Cisco" 16 | SPDX-License-Identifier = "CC-BY-4.0" 17 | 18 | [[annotations]] 19 | path = "certs/**" 20 | precedence = "aggregate" 21 | SPDX-FileCopyrightText = "Copyright (c) 2024 Cisco" 22 | SPDX-License-Identifier = "CC-BY-4.0" 23 | 24 | [[annotations]] 25 | path = [ 26 | ".clang-format", 27 | ".clang-tidy", 28 | ".github/workflows/cmake.yml", 29 | ".gitignore", 30 | ".gitmodules", 31 | "AUTHORS.md", 32 | "README.md" 33 | ] 34 | SPDX-FileCopyrightText = "NONE" 35 | SPDX-License-Identifier = "BSD-2-Clause" -------------------------------------------------------------------------------- /benchmark/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | CheckOptions: 3 | - { key: readability-identifier-naming.FunctionCase, value: aNy_CasE } 4 | 5 | InheritParentConfig: true -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | find_package(benchmark) 5 | if (NOT benchmark_FOUND) 6 | set(BENCHMARK_USE_BUNDLED_GTEST OFF) 7 | set(BENCHMARK_ENABLE_TESTING OFF) 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | benchmark 12 | GIT_REPOSITORY https://github.com/google/benchmark.git 13 | GIT_TAG main 14 | ) 15 | FetchContent_MakeAvailable(benchmark) 16 | endif() 17 | 18 | add_executable(quicr_benchmark 19 | stream_buffer.cpp 20 | time_queue.cpp 21 | uintvar.cpp 22 | hash.cpp 23 | data_storage.cpp 24 | ) 25 | 26 | target_link_libraries(quicr_benchmark PRIVATE quicr benchmark::benchmark_main) 27 | target_include_directories(quicr_benchmark PRIVATE ${PROJECT_SOURCE_DIR}/src) 28 | 29 | target_compile_options(quicr_benchmark 30 | PRIVATE 31 | $<$,$,$>: -Wpedantic -Wextra -Wall -Wno-error=unused-function> 32 | $<$: >) 33 | 34 | set_target_properties(quicr_benchmark 35 | PROPERTIES 36 | CXX_STANDARD 20 37 | CXX_STANDARD_REQUIRED YES 38 | CXX_EXTENSIONS ON) 39 | 40 | if(LINT) 41 | include(Lint) 42 | Lint(quicr_benchmark) 43 | endif() 44 | 45 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 46 | target_compile_options(quicr_benchmark PRIVATE -Wall -pedantic -Wextra -Werror -Wmissing-declarations) 47 | endif() 48 | 49 | if(MSVC) 50 | target_compile_options(quicr_benchmark PRIVATE /W4 /WX) 51 | target_compile_definitions(quicr_benchmark _CRT_SECURE_NO_WARNINGS) 52 | endif() -------------------------------------------------------------------------------- /benchmark/data_storage.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | static void 10 | DataStorage_Construct(benchmark::State& state) 11 | { 12 | for ([[maybe_unused]] const auto& _ : state) { 13 | auto buffer = quicr::DataStorage<>::Create(); 14 | benchmark::DoNotOptimize(buffer); 15 | benchmark::ClobberMemory(); 16 | } 17 | } 18 | 19 | static void 20 | DataStorage_Push(benchmark::State& state) 21 | { 22 | auto buffer = quicr::DataStorage<>::Create(); 23 | uint64_t value = 0; 24 | auto bytes = quicr::AsBytes(value); 25 | 26 | for ([[maybe_unused]] const auto& _ : state) { 27 | buffer->Push(bytes); 28 | } 29 | } 30 | 31 | BENCHMARK(DataStorage_Construct); 32 | BENCHMARK(DataStorage_Push); 33 | -------------------------------------------------------------------------------- /benchmark/hash.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using namespace quicr; 11 | using namespace std::string_literals; 12 | 13 | static void 14 | ToHash(benchmark::State& state) 15 | { 16 | TrackNamespace ns{ "example"s, "chat555"s, "user1"s, "dev1"s, "time1"s }; 17 | 18 | std::vector ns_bytes{ ns.begin(), ns.end() }; 19 | 20 | for ([[maybe_unused]] const auto& _ : state) { 21 | auto h = hash(ns_bytes); 22 | benchmark::DoNotOptimize(h); 23 | } 24 | } 25 | 26 | static void 27 | ToHashStl(benchmark::State& state) 28 | { 29 | TrackNamespace ns{ "example"s, "chat555"s, "user1"s, "dev1"s, "time1"s }; 30 | 31 | std::string_view ns_sv(reinterpret_cast(ns.data()), ns.size()); 32 | 33 | for ([[maybe_unused]] const auto& _ : state) { 34 | auto h = std::hash{}(ns_sv); 35 | benchmark::DoNotOptimize(h); 36 | benchmark::ClobberMemory(); 37 | } 38 | } 39 | 40 | BENCHMARK(ToHash); 41 | BENCHMARK(ToHashStl); 42 | -------------------------------------------------------------------------------- /benchmark/stream_buffer.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | static void 12 | StreamBuffer_Construct(benchmark::State& state) 13 | { 14 | for ([[maybe_unused]] const auto& _ : state) { 15 | auto buffer = quicr::StreamBuffer(); 16 | benchmark::DoNotOptimize(buffer); 17 | benchmark::ClobberMemory(); 18 | } 19 | } 20 | 21 | static void 22 | StreamBuffer_Push(benchmark::State& state) 23 | { 24 | quicr::StreamBuffer buffer; 25 | for ([[maybe_unused]] const auto& _ : state) { 26 | buffer.Push(std::numeric_limits::max()); 27 | } 28 | } 29 | 30 | static void 31 | StreamBuffer_PushBytes(benchmark::State& state) 32 | { 33 | std::vector buf(1280, 0); 34 | 35 | quicr::StreamBuffer buffer; 36 | for ([[maybe_unused]] const auto& _ : state) { 37 | buffer.Push(buf); 38 | } 39 | } 40 | 41 | static void 42 | StreamBuffer_PushLengthBytes(benchmark::State& state) 43 | { 44 | std::vector buf(1280, 0); 45 | 46 | quicr::StreamBuffer buffer; 47 | for ([[maybe_unused]] const auto& _ : state) { 48 | buffer.PushLengthBytes(buf); 49 | } 50 | } 51 | 52 | static void 53 | StreamBuffer_Front(benchmark::State& state) 54 | { 55 | quicr::StreamBuffer buffer; 56 | std::vector bytes(1280, 0); 57 | buffer.Push(bytes); 58 | 59 | for ([[maybe_unused]] const auto& _ : state) { 60 | auto data = buffer.Front(1280); 61 | benchmark::DoNotOptimize(data); 62 | benchmark::ClobberMemory(); 63 | } 64 | } 65 | 66 | static void 67 | SafeStreamBuffer_Construct(benchmark::State& state) 68 | { 69 | for ([[maybe_unused]] const auto& _ : state) { 70 | auto buffer = quicr::SafeStreamBuffer(); 71 | benchmark::DoNotOptimize(buffer); 72 | benchmark::ClobberMemory(); 73 | } 74 | } 75 | 76 | static void 77 | SafeStreamBuffer_Push(benchmark::State& state) 78 | { 79 | quicr::SafeStreamBuffer buffer; 80 | for ([[maybe_unused]] const auto& _ : state) { 81 | buffer.Push(std::numeric_limits::max()); 82 | } 83 | } 84 | 85 | static void 86 | SafeStreamBuffer_PushBytes(benchmark::State& state) 87 | { 88 | std::vector buf(1280, 0); 89 | 90 | quicr::SafeStreamBuffer buffer; 91 | for ([[maybe_unused]] const auto& _ : state) { 92 | buffer.Push(buf); 93 | } 94 | } 95 | 96 | static void 97 | SafeStreamBuffer_PushLengthBytes(benchmark::State& state) 98 | { 99 | std::vector buf(1280, 0); 100 | 101 | quicr::SafeStreamBuffer buffer; 102 | for ([[maybe_unused]] const auto& _ : state) { 103 | buffer.PushLengthBytes(buf); 104 | } 105 | } 106 | 107 | static void 108 | SafeStreamBuffer_Front(benchmark::State& state) 109 | { 110 | quicr::SafeStreamBuffer buffer; 111 | std::vector bytes(1280, 0); 112 | buffer.Push(bytes); 113 | 114 | for ([[maybe_unused]] const auto& _ : state) { 115 | auto data = buffer.Front(1280); 116 | benchmark::DoNotOptimize(data); 117 | benchmark::ClobberMemory(); 118 | } 119 | } 120 | 121 | BENCHMARK(StreamBuffer_Construct); 122 | BENCHMARK(StreamBuffer_Push); 123 | BENCHMARK(StreamBuffer_PushBytes); 124 | BENCHMARK(StreamBuffer_PushLengthBytes); 125 | BENCHMARK(StreamBuffer_Front); 126 | BENCHMARK(SafeStreamBuffer_Construct); 127 | BENCHMARK(SafeStreamBuffer_Push); 128 | BENCHMARK(SafeStreamBuffer_PushBytes); 129 | BENCHMARK(SafeStreamBuffer_PushLengthBytes); 130 | BENCHMARK(SafeStreamBuffer_Front); 131 | -------------------------------------------------------------------------------- /benchmark/time_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | static auto service = std::make_shared(); 6 | 7 | constexpr size_t kIterations = 1'000'000; 8 | 9 | static void 10 | TimeQueue_Push(benchmark::State& state) 11 | { 12 | quicr::TimeQueue tq(300, 1, service, kIterations); 13 | int64_t items_count = 0; 14 | 15 | for (auto _ : state) { 16 | ++items_count; 17 | tq.Push(items_count, 20); 18 | } 19 | 20 | state.SetItemsProcessed(items_count); 21 | } 22 | 23 | static void 24 | TimeQueue_Pop(benchmark::State& state) 25 | { 26 | quicr::TimeQueue tq(300, 1, service, kIterations); 27 | for (size_t i = 0; i < kIterations; ++i) { 28 | tq.Push(i, 10); 29 | } 30 | 31 | int64_t items_count = 0; 32 | for (auto _ : state) { 33 | ++items_count; 34 | tq.Pop(); 35 | } 36 | 37 | state.SetItemsProcessed(items_count); 38 | } 39 | 40 | static void 41 | TimeQueue_PopFront(benchmark::State& state) 42 | { 43 | quicr::TimeQueue tq(300, 1, service, kIterations); 44 | for (size_t i = 0; i < kIterations; ++i) { 45 | tq.Push(i, 15); 46 | } 47 | 48 | int64_t items_count = 0; 49 | for (auto _ : state) { 50 | ++items_count; 51 | auto value = tq.PopFront(); 52 | benchmark::DoNotOptimize(value); 53 | benchmark::ClobberMemory(); 54 | } 55 | 56 | state.SetItemsProcessed(items_count); 57 | } 58 | 59 | BENCHMARK(TimeQueue_Push)->Iterations(kIterations); 60 | BENCHMARK(TimeQueue_Pop)->Iterations(kIterations); 61 | BENCHMARK(TimeQueue_PopFront)->Iterations(kIterations); -------------------------------------------------------------------------------- /benchmark/uintvar.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | static void 11 | UIntVar_FromUint64(benchmark::State& state) 12 | { 13 | for ([[maybe_unused]] const auto& _ : state) { 14 | auto value = quicr::UintVar(0x3FFFFFFFFFFFFFFF); 15 | benchmark::DoNotOptimize(value); 16 | benchmark::ClobberMemory(); 17 | } 18 | } 19 | 20 | static void 21 | UIntVar_ToUint64(benchmark::State& state) 22 | { 23 | constexpr auto var_int = quicr::UintVar(0x123456789); 24 | for ([[maybe_unused]] const auto& _ : state) { 25 | auto value = uint64_t(var_int); 26 | benchmark::DoNotOptimize(value); 27 | benchmark::ClobberMemory(); 28 | } 29 | } 30 | 31 | static void 32 | UIntVar_ToBytes(benchmark::State& state) 33 | { 34 | constexpr auto var_int = quicr::UintVar(0x123456789); 35 | for ([[maybe_unused]] const auto& _ : state) { 36 | auto bytes = std::span{ var_int }; 37 | benchmark::DoNotOptimize(bytes); 38 | benchmark::ClobberMemory(); 39 | } 40 | } 41 | 42 | static void 43 | UIntVar_FromBytes(benchmark::State& state) 44 | { 45 | constexpr auto var = quicr::UintVar(0x123456789); 46 | constexpr auto bytes = std::bit_cast>(var); 47 | for ([[maybe_unused]] const auto& _ : state) { 48 | auto value = quicr::UintVar(bytes); 49 | benchmark::DoNotOptimize(value); 50 | benchmark::ClobberMemory(); 51 | } 52 | } 53 | 54 | BENCHMARK(UIntVar_FromUint64); 55 | BENCHMARK(UIntVar_ToUint64); 56 | BENCHMARK(UIntVar_ToBytes); 57 | BENCHMARK(UIntVar_FromBytes); 58 | -------------------------------------------------------------------------------- /build.Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # Build libquicr 5 | # 6 | # BUILD: 7 | # DOCKER_BUILDKIT=1 8 | # tar -c -C ../ ./quicrq ./libquicr \ 9 | # | docker buildx build --progress=plain \ 10 | # --output type=docker \ 11 | # -f libquicr/build.Dockerfile -t quicr/libquicr:latest - 12 | # 13 | # RUN and build code 14 | # docker run --rm \ 15 | # -v $(pwd):/ws/src -v $(pwd)/../:/ws/libs \ 16 | # quicr/libquicr:latest 17 | # 18 | FROM alpine:latest 19 | 20 | VOLUME /ws/src 21 | VOLUME /ws/libs 22 | 23 | WORKDIR /ws/src 24 | 25 | COPY libquicr/ /ws/src 26 | COPY quicrq /ws/quicrq 27 | 28 | 29 | RUN apk update \ 30 | && apk add --no-cache cmake alpine-sdk openssl-dev doctest-dev bash \ 31 | && apk add ca-certificates clang lld curl 32 | 33 | # Build PicoTLS, PicoQuic, and quicrq 34 | RUN cd /ws \ 35 | && echo "Building PicoTLS" \ 36 | && git clone https://github.com/h2o/picotls.git \ 37 | && cd picotls \ 38 | && git submodule init \ 39 | && git submodule update \ 40 | && cmake . \ 41 | && make 42 | 43 | RUN cd /ws \ 44 | && echo "Building PicoQuic" \ 45 | && git clone https://github.com/private-octopus/picoquic.git \ 46 | && cd picoquic \ 47 | && cmake . \ 48 | && make 49 | 50 | RUN echo "Building Quicrq" \ 51 | && cd /ws/quicrq \ 52 | && rm -rf CMakeCache.txt CMakeFiles build \ 53 | && cmake . \ 54 | && make 55 | 56 | -------------------------------------------------------------------------------- /certs/LICENSE: -------------------------------------------------------------------------------- 1 | The work in this direcotry and sub directories under this directory (c) 2 | 2024 by Cisco is licensed under Creative Commons Attribution 4.0 3 | International. To view a copy of this license, visit 4 | https://creativecommons.org/licenses/by/4.0/ 5 | -------------------------------------------------------------------------------- /certs/badcert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv 3 | dGxzIHRlc3QgY2EwHhcNMTgwMjIzMDIzODEyWhcNMjgwMjIxMDIzODEyWjAbMRkw 4 | FwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEA5soWzSG7iyawQlHM1yaX2dUAATUkhpbg2WPFOEem7E3zYzc6A/Z+ 6 | bViFlfEgL37cbDUb4pnOAHrrsjGgkyBYh5i9iCTVfCk+H6SOHZJORO1Tq8X9C7Wc 7 | NcshpSdm2Pa8hmv9hsHbLSeoPNeg8NkTPwMVaMZ2GpdmiyAmhzSZ2H9mzNI7ntPW 8 | /XCchVf+ax2yt9haZ+mQE2NPYwHDjqCtdGkP5ZXXnYhJSBzSEhxfGckIiKDyOxiN 9 | kLFLvUdT4ERSFBjauP2cSI0XoOUsiBxJNwHH310AU8jZbveSTcXGYgEuu2MIuDo7 10 | Vhkq5+TCqXsIFNbjy0taOoPRvUbPsbqFlQIDAQABo3sweTAJBgNVHRMEAjAAMCwG 11 | CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV 12 | HQ4EFgQUE1vXDjBT8j2etP4brfHQ9DeKnpgwHwYDVR0jBBgwFoAUv3nKl7JgeCCW 13 | qkZXnN+nsiP1JWMwDQYJKoZIhvcNAQELBQADggEBAKwARsxOCiGPXU1xhvs+pq9I 14 | 63mLi4rfnssOGzGnnAfuEaxggpozf3fOSgfyTaDbACdRPTZEStjQ5HMCcHvY7CH0 15 | 8EYA+lkmFbuXXL8uHby1JBTzbTGf8pkRUsuF/Ie0SLChoDgt8oF3mY5pyU4HUaAw 16 | Zp6HBpIRMdmbwGcwm25bl9MQYTrTX3dBfp3XPzfXbVwjJ7bsiTwAGq+dKwzwOQeM 17 | 2ZMZt4BQBoevsNopPrqG0S6kGUmJOIax0t13bKxDj21+Hp/O90HTFVCtAaDxRC56 18 | k0O8Q62ZxzjGJ7Zw6K3azXlH/BYE+CajxTUF+FKRRkkWL1GrFVUsYd9KLDAVry0= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv 3 | dGxzIHRlc3QgY2EwHhcNMTgwMjIzMDIzODEyWhcNMjgwMjIxMDIzODEyWjAbMRkw 4 | FwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEA5soWzSG7iyawQlHM1yaX2dUAATUkhpbg2WPFOEem7E3zYzc6A/Z+ 6 | bViFlfEgL37cbDUb4pnOAHrrsjGgkyBYh5i9iCTVfCk+H6SOHZJORO1Tq8X9C7Wc 7 | NcshpSdm2Pa8hmv9hsHbLSeoPNeg8NkTPwMVaMZ2GpdmiyAmhzSZ2H9mzNI7ntPW 8 | /XCchVf+ax2yt9haZ+mQE2NPYwHDjqCtdGkP5ZXXnYhJSBzSEhxfGckIiKDyOxiN 9 | kLFLvUdT4ERSFBjauP2cSI0XoOUsiBxJNwHH310AU8jZbveSTcXGYgEuu2MIuDo7 10 | Vhkq5+TCqXsIFNbjy0taOoPRvUbPsbqFlQIDAQABo3sweTAJBgNVHRMEAjAAMCwG 11 | CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV 12 | HQ4EFgQUE1vXDjBT8j2etP4brfHQ9DeKnpgwHwYDVR0jBBgwFoAUv3nKl7JgeCCW 13 | qkZXnN+nsiP1JWMwDQYJKoZIhvcNAQELBQADggEBAKwARsxOCiGPXU1xhvs+pq9I 14 | 63mLi4rfnssOGzGnnAfuEaxggpozf3fOSgfyTaDbACdRPTZEStjQ5HMCcHvY7CH0 15 | 8EYA+lkmFbuXXL8uHby1JBTzbTGf8pkRUsuF/Ie0SLChoDgt8oF3mY5pyU4HUaAw 16 | Zp6HBpIRMdmbwGcwm25bl9MQYTrTX3dBfp3XPzfXbVwjJ7bsiTwAGq+dKwzwOQeM 17 | 2ZMZt4BQBoevsNopPrqG0S6kGUmJOIax0t13bKwDj21+Hp/O90HTFVCtAaDxRC56 18 | k0O8Q62ZxzjGJ7Zw6K3azXlH/BYE+CajxTUF+FKRRkkWL1GrFVUsYd9KLDAVry0= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/esni-rr.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quicr/libquicr/c4ff949cb3ec5f5863033a6956cc19765e901fcb/certs/esni-rr.bin -------------------------------------------------------------------------------- /certs/esni-secp256r1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIFl7QXlpyp2ml+Q2hRFYbif5DBNYwYZgf8GX7lk2Q3/GoAoGCCqGSM49 3 | AwEHoUQDQgAE0dm4mgemwMNUSJ/3FWrbmZieylyXqRVqIO3tv+9izvB4BysNvera 4 | TjOZREK3q99JAR1WhfmGYSNn0lkOYsCb2g== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDmyhbNIbuLJrBC 3 | UczXJpfZ1QABNSSGluDZY8U4R6bsTfNjNzoD9n5tWIWV8SAvftxsNRvimc4Aeuuy 4 | MaCTIFiHmL2IJNV8KT4fpI4dkk5E7VOrxf0LtZw1yyGlJ2bY9ryGa/2GwdstJ6g8 5 | 16Dw2RM/AxVoxnYal2aLICaHNJnYf2bM0jue09b9cJyFV/5rHbK32Fpn6ZATY09j 6 | AcOOoK10aQ/lldediElIHNISHF8ZyQiIoPI7GI2QsUu9R1PgRFIUGNq4/ZxIjReg 7 | 5SyIHEk3AcffXQBTyNlu95JNxcZiAS67Ywi4OjtWGSrn5MKpewgU1uPLS1o6g9G9 8 | Rs+xuoWVAgMBAAECggEBAJZxS/XCNH/b43AH5LCnbrtH1u3yl3HIrp/nIquyQYSu 9 | t6aIXKAysW1UFBiPCz0KxGMhJ6FKQ3gaqMQLB7KAllUl4v75i9SZCe8UlLOAKNdT 10 | oYRK1s4oP8DtPmxrR+bMyE4T3TtX6SkBPfETWs1Fo/8iYnVfUaO559VvSs4+Ir91 11 | 6EVBZ+xSQ6+H4nc9TvrByd7HHMmDGFXF19eo1rtQOxbNMiZSNwch+eFdzUr+hIqT 12 | oeMiZCL+gTiWCglQIQNpRurSdc2szQSZNO85DlNU/kt2z2nxQMRrnl5mWUZdqbTM 13 | qOEMq2RD/lR+fGwC/Zp1jly4f5c3QTF8oPzAhnmog20CgYEA9FtAZlmQ/8c5gDhh 14 | n0dgBRDAVL0hmtDxjsL9RsvTVeWLXecpqUfFd0bpm/pi/QUIkoDc33jmUSh/2vCu 15 | LCG5lvKsDVNwqiT+zsdHwGFyybYPNEPk6ILccr5fL7p8Fox5t9+oE+nct+P+VI82 16 | 3TD/wN2soFPD9wDLjSDSfXMS7TsCgYEA8clXgh9AGJuUKoOHvsV9BFQ1HiFVtuXT 17 | Qa+KKCLDnreeTuzF2s4nV51PJM5QEe0zfOABP2xjPYJRe+zIQ+rMwMA1Yxv17ZH/ 18 | 81w/poziTtZBGfCRmSnRY4gf3lTajUwcJhzmZ4AdV/ZQyX7rjFTZl9jeuMyY8uNY 19 | y3SEpiw6a28CgYAKUhBWQlIte2yiTb9RyuHzVNHKwnI457pMHVA1PUafyiIoxSqt 20 | S6q7bvNO8zRbG2tRRMAPcDvKEbvUs3Wnx4TfK0C5D10i0o0wjpopNfRzMI1T18pD 21 | R8On1QKQMYAsM6KwcXHX5Xi9C5QiXiojDX6/1p0D6IXOWOo/+7LoOYQDIQKBgDJY 22 | KB5x/1igXHOVu5gfau6R0hWZ/0z8Acb1lCDTTEQqG453gqMSteJqYOZbBxUUfNoN 23 | knTwTqGqFulk3jY2F7gyzWr7kXOMKO01UhON1jlwJ1INY2Ou72h4GZqjtHYjWOEe 24 | t2LprDJ6mUu7X7RynnQdthJol5hLeluywUQQhYGFAoGAbfGV4PhKD2sbY5ZDPkiu 25 | 8sWD8fLthbK6yFV7PrWRl4sOe32Iza6bvqT/EU0hbRr54vZXFFMJtev3PzqGNz/o 26 | J5IkcpgVCXjOxIBmRognT4duuEM0EBiH1vaZF1f44x1ntnkncaW3wQ0VGp7xGURI 27 | ykArGNH50gPRuPACNWvYoKE= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /certs/test-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDMjCCAhqgAwIBAgIJAPh4W88oNy7tMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV 3 | BAMTD3BpY290bHMgdGVzdCBjYTAeFw0xODAyMjMwMjI0NDJaFw0yODAyMjEwMjI0 4 | NDJaMBoxGDAWBgNVBAMTD3BpY290bHMgdGVzdCBjYTCCASIwDQYJKoZIhvcNAQEB 5 | BQADggEPADCCAQoCggEBAMewDZVDfz1PFT4TfGvG4uF27Tv7w64/bxFB0ZK4Wjpj 6 | eMxdiWBrw7dyZ9KAqxrcIw0KLBqeVRUvokTNSLGLM7j6CVMNFtH0dKqQ7hef9xB8 7 | NSPoNkTMs/Cf2te79ifOCd0+QHlIWi7Qzt2Ito+sKzuACFP+8zXIkksxHWGLLNSz 8 | Q0PfmDHNp+WnoTmTDIcwjhfhb3PUZVNZONFhVjXgrkCqgbutna96InsN/7TWGotT 9 | xSjb2xOuSSvoueCYGSFFb5a9UVMwWbAmquc8KnhTAvqwCa8QbaiOVujUWCL2k0H4 10 | EVlkzn+QfIiDNRk28SvwazcOtz7HPj795XwMYXPXiKcCAwEAAaN7MHkwHQYDVR0O 11 | BBYEFL95ypeyYHgglqpGV5zfp7Ij9SVjMEoGA1UdIwRDMEGAFL95ypeyYHgglqpG 12 | V5zfp7Ij9SVjoR6kHDAaMRgwFgYDVQQDEw9waWNvdGxzIHRlc3QgY2GCCQD4eFvP 13 | KDcu7TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBQ9EyGzIm8uX8U 14 | MIYkvGyQiSAl4v7Y9PZhtJIbuSn/hV8rutKs550AMFpPL5tijNpyUvZyR+Wpuvs9 15 | TGrOPIFhetcBF3tVUsg4lVvhMcxojUKysv0UwfEJQVbu1yoZmRdXOnKGiVnqvpI8 16 | ZjcgNtMacoBViQV44cR805bu6zBNWLaac3Q1wgT9NQSdBuQp0tAzVFQkE3ZRigfT 17 | LdQMb73jddaWZG8wnDfebK0klZo2oif2kGq53OOBooN/QUWKinMPPWdQVcY5Texa 18 | TmOVYk7HnWQEQ+Wr+9/o8EUs+3B/Af7lV7q9redWIiyYdyKPKmx090XHBy6HTPyO 19 | o9citOWg 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /cmake/FindMbedTLS.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | set(MBEDTLS_HEADERS mbedtls/ssl.h) 5 | if (PLATFORM_ESP_IDF) 6 | list(APPEND MBEDTLS_HEADERS mbedtls/esp_config.h) 7 | endif() 8 | find_path(MBEDTLS_INCLUDE_DIRS ${MBEDTLS_HEADERS}) 9 | 10 | find_library(MBEDTLS_LIBRARY mbedtls) 11 | find_library(MBEDX509_LIBRARY mbedx509) 12 | find_library(MBEDCRYPTO_LIBRARY mbedcrypto) 13 | 14 | set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") 15 | 16 | include(FindPackageHandleStandardArgs) 17 | find_package_handle_standard_args(MbedTLS DEFAULT_MSG 18 | MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) 19 | 20 | mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) 21 | -------------------------------------------------------------------------------- /cmake/Lint.cmake: -------------------------------------------------------------------------------- 1 | function(lint target) 2 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 3 | find_program(CLANG_TIDY_EXE clang-tidy REQUIRED) 4 | set_target_properties(${target} 5 | PROPERTIES 6 | CXX_CLANG_TIDY "${CLANG_TIDY_EXE}" 7 | CXX_CLANG_TIDY_EXPORT_FIXES_DIR "clang-tidy-fixes") 8 | endfunction() 9 | -------------------------------------------------------------------------------- /cmd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | add_subdirectory(examples) 5 | add_subdirectory(qperf) 6 | add_subdirectory(qperf2) 7 | -------------------------------------------------------------------------------- /cmd/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | include(FetchContent) 5 | 6 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) 7 | FetchContent_MakeAvailable(json) 8 | 9 | add_executable(qclient client.cpp) 10 | target_link_libraries(qclient PRIVATE quicr nlohmann_json::nlohmann_json) 11 | target_include_directories(qclient PRIVATE ../dependencies ) 12 | 13 | target_compile_options(qclient 14 | PRIVATE 15 | $<$,$,$>: -Wpedantic -Wextra -Wall> 16 | $<$: >) 17 | 18 | set_target_properties(qclient 19 | PROPERTIES 20 | CXX_STANDARD 20 21 | CXX_STANDARD_REQUIRED YES 22 | CXX_EXTENSIONS OFF) 23 | 24 | target_compile_definitions(qclient PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 25 | 26 | add_executable(qserver 27 | server.cpp 28 | ) 29 | target_link_libraries(qserver PRIVATE quicr) 30 | target_include_directories(qserver PRIVATE ../dependencies ) 31 | 32 | target_compile_options(qserver 33 | PRIVATE 34 | $<$,$,$>: -Wpedantic -Wextra -Wall> 35 | $<$: >) 36 | 37 | set_target_properties(qserver 38 | PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED YES 41 | CXX_EXTENSIONS ON) 42 | 43 | target_compile_definitions(qserver PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 44 | 45 | if(LINT) 46 | include(Lint) 47 | Lint(qserver) 48 | Lint(qclient) 49 | endif() -------------------------------------------------------------------------------- /cmd/examples/helper_functions.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace quicr::example { 12 | 13 | /** 14 | * @brief Get UTC timestamp as a string 15 | * 16 | * @return string value of UTC time 17 | */ 18 | static std::string GetTimeStr() noexcept 19 | { 20 | std::ostringstream oss; 21 | 22 | auto now = std::chrono::system_clock::now(); 23 | auto now_us = std::chrono::time_point_cast(now); 24 | std::time_t t = std::chrono::system_clock::to_time_t(now); 25 | struct tm tm_result; 26 | localtime_r(&t, &tm_result); 27 | oss << std::put_time(&tm_result, "%F %T") << "." << std::setfill('0') << std::setw(6) 28 | << (now_us.time_since_epoch().count()) % 1'000'000; 29 | 30 | return oss.str(); 31 | } 32 | 33 | /** 34 | * @brief Create a full track name using strings for namespace and name 35 | * 36 | * @param track_namespace track namespace as a string 37 | * @param track_name track name as a string 38 | * @param track_alias track alias as optional 39 | * @return quicr::FullTrackName of the params 40 | */ 41 | static FullTrackName const MakeFullTrackName(const std::string& track_namespace, 42 | const std::string& track_name, 43 | const std::optional track_alias) noexcept 44 | { 45 | const auto split = [](std::string str, const std::string& delimiter) { 46 | std::vector tokens; 47 | 48 | std::size_t pos = 0; 49 | while ((pos = str.find(delimiter)) != std::string::npos) { 50 | tokens.emplace_back(str.substr(0, pos)); 51 | str.erase(0, pos + delimiter.length()); 52 | } 53 | tokens.emplace_back(std::move(str)); 54 | 55 | return tokens; 56 | }; 57 | 58 | FullTrackName full_track_name{ TrackNamespace{ split(track_namespace, ",") }, 59 | { track_name.begin(), track_name.end() }, 60 | track_alias }; 61 | return full_track_name; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/examples/signal_handler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace moq_example { 12 | std::mutex main_mutex; // Main's mutex 13 | bool terminate{ false }; // Termination flag 14 | std::condition_variable cv; // Main thread waits on this 15 | const char* termination_reason{ nullptr }; // Termination reason 16 | }; 17 | 18 | /* 19 | * signalHandler 20 | * 21 | * Description: 22 | * This function will handle operating system signals related to 23 | * termination and then instruct the main thread to terminate. 24 | * 25 | * Parameters: 26 | * signal_number [in] 27 | * The signal caught. 28 | * 29 | * Returns: 30 | * Nothing. 31 | * 32 | * Comments: 33 | * None. 34 | */ 35 | void 36 | signalHandler(int signal_number) 37 | { 38 | const auto lock = std::lock_guard(moq_example::main_mutex); 39 | 40 | // If termination is in process, just return 41 | if (moq_example::terminate) { 42 | return; 43 | } 44 | 45 | // Indicate that the process should terminate 46 | moq_example::terminate = true; 47 | 48 | // Set the termination reason string 49 | switch (signal_number) { 50 | case SIGINT: 51 | moq_example::termination_reason = "Interrupt signal received"; 52 | break; 53 | 54 | #ifndef _WIN32 55 | case SIGHUP: 56 | moq_example::termination_reason = "Hangup signal received"; 57 | break; 58 | 59 | case SIGQUIT: 60 | moq_example::termination_reason = "Quit signal received"; 61 | break; 62 | #endif 63 | 64 | default: 65 | moq_example::termination_reason = "Unknown signal received"; 66 | break; 67 | } 68 | 69 | // Notify the main execution thread to terminate 70 | moq_example::cv.notify_all(); 71 | } 72 | 73 | /* 74 | * installSignalHandlers 75 | * 76 | * Description: 77 | * This function will install the signal handlers for SIGINT, SIGQUIT, 78 | * etc. so that the process can be terminated in a controlled fashion. 79 | * 80 | * Parameters: 81 | * None. 82 | * 83 | * Returns: 84 | * Nothing. 85 | * 86 | * Comments: 87 | * None. 88 | */ 89 | void 90 | installSignalHandlers() 91 | { 92 | #ifdef _WIN32 93 | if (signal(SIGINT, signalHandler) == SIG_ERR) { 94 | std::cerr << "Failed to install SIGINT handler" << std::endl; 95 | } 96 | #else 97 | struct sigaction sa = {}; 98 | 99 | // Configure the sigaction struct 100 | sa.sa_handler = signalHandler; 101 | sigemptyset(&sa.sa_mask); 102 | sa.sa_flags = 0; 103 | 104 | // Catch SIGHUP (signal 1) 105 | if (sigaction(SIGHUP, &sa, nullptr) == -1) { 106 | std::cerr << "Failed to install SIGHUP handler" << std::endl; 107 | } 108 | 109 | // Catch SIGINT (signal 2) 110 | if (sigaction(SIGINT, &sa, nullptr) == -1) { 111 | std::cerr << "Failed to install SIGINT handler" << std::endl; 112 | } 113 | 114 | // Catch SIGQUIT (signal 3) 115 | if (sigaction(SIGQUIT, &sa, nullptr) == -1) { 116 | std::cerr << "Failed to install SIGQUIT handler" << std::endl; 117 | } 118 | #endif 119 | } 120 | -------------------------------------------------------------------------------- /cmd/qperf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | add_executable(qperf qperf.cpp) 5 | target_link_libraries(qperf PRIVATE quicr) 6 | target_include_directories(qperf PRIVATE ../dependencies ) 7 | 8 | target_compile_options(qperf 9 | PRIVATE 10 | $<$,$,$>: -Wpedantic -Wextra -Wall> 11 | $<$: >) 12 | 13 | set_target_properties(qperf 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS OFF) 18 | 19 | target_compile_definitions(qperf PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 20 | 21 | if(LINT) 22 | include(Lint) 23 | Lint(qperf) 24 | endif() 25 | -------------------------------------------------------------------------------- /cmd/qperf2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | add_executable(qperf_pub qperf_pub.cc) 5 | target_link_libraries(qperf_pub PRIVATE quicr) 6 | target_include_directories(qperf_pub PRIVATE ../dependencies ) 7 | 8 | target_compile_options(qperf_pub 9 | PRIVATE 10 | $<$,$,$>: -Wpedantic -Wextra -Wall> 11 | $<$: >) 12 | 13 | set_target_properties(qperf_pub 14 | PROPERTIES 15 | CXX_STANDARD 20 16 | CXX_STANDARD_REQUIRED YES 17 | CXX_EXTENSIONS OFF) 18 | 19 | target_compile_definitions(qperf_pub PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 20 | 21 | if(LINT) 22 | include(Lint) 23 | Lint(qperf_pub) 24 | endif() 25 | 26 | add_executable(qperf_sub qperf_sub.cc) 27 | target_link_libraries(qperf_sub PRIVATE quicr) 28 | target_include_directories(qperf_sub PRIVATE ../dependencies ) 29 | 30 | target_compile_options(qperf_sub 31 | PRIVATE 32 | $<$,$,$>: -Wpedantic -Wextra -Wall> 33 | $<$: >) 34 | 35 | set_target_properties(qperf_sub 36 | PROPERTIES 37 | CXX_STANDARD 20 38 | CXX_STANDARD_REQUIRED YES 39 | CXX_EXTENSIONS OFF) 40 | 41 | target_compile_definitions(qperf_sub PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 42 | 43 | if(LINT) 44 | include(Lint) 45 | Lint(qperf_sub) 46 | endif() 47 | -------------------------------------------------------------------------------- /cmd/qperf2/config.ini: -------------------------------------------------------------------------------- 1 | [TRACK 1] 2 | namespace = perf/1 ; track namespace 3 | name = 1 ; track name 4 | track_mode = stream ; track mode {datagram,stream} 5 | priority = 2 ; priority (0-255) 6 | ttl = 5000 ; ttl in ms 7 | time_interval = 33.33 ; transmit interval in floating point ms 8 | objs_per_group = 150 ; objects per group count >=1 9 | bytes_per_group_start = 100000 ; size of a group 0 object 10 | bytes_per_group = 5000 ; size of a group <> 0 object 11 | start_delay = 5000 ; start delay in ms - after subscribes 12 | total_test_time = 25000 ; total transmit time in ms - including startdelay 13 | ; (not configured): total_transmit_time - is calculated total_transmit_time = total_test_time - start_delay 14 | 15 | [TRACK 2] 16 | namespace = perf/2 ; track namespace 17 | name = 1 ; track name 18 | track_mode = datagram ; track mode {datagram,stream} 19 | priority = 1 ; priority (0-255) 20 | ttl = 5000 ; ttl in ms 21 | time_interval = 20.00 ; transmit interval in floating point ms 22 | objs_per_group = 1 ; objects per group count >=1 23 | bytes_per_group_start = 60 ; size of a group 0 object 24 | bytes_per_group = 60 ; size of a group <> 0 object 25 | end_delay = 5000 ; start delay in ms - after subscribes 26 | start_delay = 10000 ; start delay in ms - after subscribes 27 | total_test_time = 35000 ; total transmit time in ms - including startdelay 28 | -------------------------------------------------------------------------------- /cmd/qperf2/qperf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "inicpp.h" 7 | 8 | namespace qperf { 9 | struct PerfConfig 10 | { 11 | std::string test_name; 12 | quicr::FullTrackName full_track_name; 13 | quicr::TrackMode track_mode; 14 | uint8_t priority; 15 | uint32_t ttl; 16 | double transmit_interval; 17 | uint32_t objects_per_group; 18 | uint32_t bytes_per_group_start; 19 | uint32_t bytes_per_group; 20 | uint64_t start_delay; 21 | uint64_t total_test_time; 22 | uint64_t total_transmit_time; 23 | }; 24 | 25 | enum class TestMode : uint8_t 26 | { 27 | kNone, 28 | kWaitPreTest, 29 | kRunning, 30 | kComplete, 31 | kwaitPostTest, 32 | kError 33 | }; 34 | 35 | struct TestMetrics 36 | { 37 | std::uint64_t start_transmit_time; 38 | std::uint64_t end_transmit_time; 39 | std::uint64_t total_published_objects; 40 | std::uint64_t total_objects_dropped_not_ok; 41 | std::uint64_t total_published_bytes; 42 | std::uint64_t max_publish_bitrate; 43 | std::uint64_t min_publish_bitrate; 44 | std::uint64_t avg_publish_bitrate; 45 | std::uint32_t metric_samples; 46 | std::uint64_t bitrate_total; 47 | }; 48 | 49 | struct ObjectTestHeader 50 | { 51 | TestMode test_mode; 52 | std::uint64_t time; 53 | }; 54 | 55 | struct ObjectTestComplete 56 | { 57 | TestMode test_mode; 58 | std::uint64_t time; 59 | TestMetrics test_metrics; 60 | }; 61 | 62 | inline quicr::FullTrackName MakeFullTrackName(const std::string& track_namespace, 63 | const std::string& track_name, 64 | const std::optional track_alias = std::nullopt) noexcept 65 | { 66 | return { 67 | quicr::TrackNamespace{ track_namespace }, 68 | { track_name.begin(), track_name.end() }, 69 | track_alias, 70 | }; 71 | } 72 | 73 | static bool PopulateScenarioFields(const std::string section_name, ini::IniFile& inif, PerfConfig& perf_config) 74 | { 75 | bool parsed = false; 76 | std::string scenario_namespace = ""; 77 | std::string scenario_name = ""; 78 | 79 | perf_config.test_name = section_name; 80 | 81 | scenario_namespace = inif[section_name]["namespace"].as(); 82 | scenario_name = inif[section_name]["name"].as(); 83 | perf_config.full_track_name = MakeFullTrackName(scenario_namespace, scenario_name); 84 | 85 | std::string track_mode_ini_str = inif[section_name]["track_mode"].as(); 86 | if (track_mode_ini_str == "datagram") { 87 | perf_config.track_mode = quicr::TrackMode::kDatagram; 88 | } else if (track_mode_ini_str == "stream") { 89 | perf_config.track_mode = quicr::TrackMode::kStream; 90 | } else { 91 | perf_config.track_mode = quicr::TrackMode::kStream; 92 | SPDLOG_WARN("Invalid or missing track mode in scenario. Using default `stream`"); 93 | } 94 | 95 | perf_config.priority = inif[section_name]["priority"].as(); 96 | perf_config.ttl = inif[section_name]["ttl"].as(); 97 | perf_config.transmit_interval = inif[section_name]["time_interval"].as(); 98 | perf_config.objects_per_group = inif[section_name]["objs_per_group"].as(); 99 | perf_config.bytes_per_group_start = inif[section_name]["bytes_per_group_start"].as(); 100 | perf_config.bytes_per_group = inif[section_name]["bytes_per_group"].as(); 101 | perf_config.start_delay = inif[section_name]["start_delay"].as(); 102 | perf_config.total_test_time = inif[section_name]["total_test_time"].as(); 103 | perf_config.total_transmit_time = perf_config.total_test_time - perf_config.start_delay; 104 | 105 | SPDLOG_INFO("--------------------------------------------"); 106 | SPDLOG_INFO("Test config:"); 107 | SPDLOG_INFO(" ns \"{}\"", scenario_namespace); 108 | SPDLOG_INFO(" n \"{}\"", scenario_name); 109 | SPDLOG_INFO(" track mode {} ({})", (int)perf_config.track_mode, track_mode_ini_str); 110 | SPDLOG_INFO(" pri {}", perf_config.priority); 111 | SPDLOG_INFO(" ttl {}", perf_config.ttl); 112 | SPDLOG_INFO(" objspergroup {}", perf_config.objects_per_group); 113 | SPDLOG_INFO(" bytes per group start {}", perf_config.bytes_per_group_start); 114 | SPDLOG_INFO(" bytes per group {}", perf_config.bytes_per_group); 115 | SPDLOG_INFO(" transmit interval {}", perf_config.transmit_interval); 116 | SPDLOG_INFO(" start_delay {}", perf_config.start_delay); 117 | SPDLOG_INFO(" total test time {}", perf_config.total_test_time); 118 | SPDLOG_INFO(" transmit time {}", perf_config.total_transmit_time); 119 | SPDLOG_INFO("--------------------------------------------"); 120 | 121 | parsed = true; 122 | return parsed; 123 | } 124 | 125 | inline std::string FormatBitrate(const std::uint32_t& bitrate) 126 | { 127 | if (bitrate > 1e9) { 128 | return std::to_string(double(bitrate) / 1e9) + " Gbps"; 129 | } 130 | if (bitrate > 1e6) { 131 | return std::to_string(double(bitrate) / 1e6) + " Mbps"; 132 | } 133 | if (bitrate > 1e3) { 134 | return std::to_string(double(bitrate) / 1e3) + " Kbps"; 135 | } 136 | 137 | return std::to_string(bitrate) + " bps"; 138 | } 139 | 140 | /** 141 | * @brief Publish track handler 142 | * @details Publish track handler used for the publish command line option 143 | */ 144 | 145 | } // namespace qperf 146 | -------------------------------------------------------------------------------- /cmd/qperf2/qperf_pub.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "inicpp.h" 7 | #include "qperf.hpp" 8 | #include 9 | 10 | namespace qperf { 11 | class PerfPublishTrackHandler : public quicr::PublishTrackHandler 12 | { 13 | private: 14 | PerfPublishTrackHandler(const PerfConfig&); 15 | 16 | public: 17 | static auto Create(const std::string& section_name, ini::IniFile& inif); 18 | void StatusChanged(Status status) override; 19 | void MetricsSampled(const quicr::PublishTrackMetrics& metrics) override; 20 | 21 | qperf::TestMode TestMode() { return test_mode_; } 22 | 23 | std::chrono::time_point PublishObjectWithMetrics(quicr::BytesSpan object_span); 24 | std::uint64_t PublishTestComplete(); 25 | 26 | std::thread SpawnWriter(); 27 | void WriteThread(); 28 | void StopWriter(); 29 | 30 | bool IsComplete() { return (test_mode_ == qperf::TestMode::kComplete); } 31 | 32 | private: 33 | PerfConfig perf_config_; 34 | std::atomic_bool terminate_; 35 | uint64_t last_bytes_; 36 | qperf::TestMode test_mode_; 37 | uint64_t group_id_; 38 | uint64_t object_id_; 39 | 40 | std::thread write_thread_; 41 | std::chrono::time_point last_metric_time_; 42 | 43 | qperf::TestMetrics test_metrics_; 44 | std::mutex mutex_; 45 | }; 46 | 47 | class PerfPubClient : public quicr::Client 48 | { 49 | public: 50 | PerfPubClient(const quicr::ClientConfig& cfg, const std::string& configfile); 51 | void StatusChanged(Status status) override; 52 | void MetricsSampled(const quicr::ConnectionMetrics&) override; 53 | bool GetTerminateStatus(); 54 | bool HandlersComplete(); 55 | void Terminate(); 56 | 57 | private: 58 | bool terminate_; 59 | std::string configfile_; 60 | ini::IniFile inif_; 61 | std::vector> track_handlers_; 62 | std::mutex track_handlers_mutex_; 63 | }; 64 | 65 | } // namespace qperf 66 | -------------------------------------------------------------------------------- /cmd/qperf2/qperf_sub.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "inicpp.h" 7 | #include "qperf.hpp" 8 | 9 | namespace qperf { 10 | class PerfSubscribeTrackHandler : public quicr::SubscribeTrackHandler 11 | { 12 | private: 13 | PerfSubscribeTrackHandler(const PerfConfig& perf_config, std::uint32_t test_identifier); 14 | 15 | public: 16 | static auto Create(const std::string& section_name, ini::IniFile& inif, std::uint32_t test_identifier); 17 | void ObjectReceived(const quicr::ObjectHeaders&, quicr::BytesSpan) override; 18 | void StatusChanged(Status status) override; 19 | void MetricsSampled(const quicr::SubscribeTrackMetrics& metrics) override; 20 | const quicr::SubscribeTrackMetrics& GetMetrics() const noexcept { return metrics_; } 21 | 22 | bool IsComplete() { return terminate_; } 23 | 24 | std::string TestName() { return perf_config_.test_name; } 25 | 26 | private: 27 | std::atomic_bool terminate_; 28 | PerfConfig perf_config_; 29 | quicr::SubscribeTrackMetrics metrics_; 30 | bool first_pass_; 31 | std::chrono::time_point last_metric_time_; 32 | uint64_t last_bytes_; 33 | std::uint64_t local_now_; 34 | std::uint64_t last_local_now_; 35 | std::uint64_t start_data_time_; 36 | std::uint64_t total_objects_; 37 | std::uint64_t total_bytes_; 38 | std::uint32_t test_identifier_; 39 | qperf::TestMode test_mode_; 40 | 41 | std::uint64_t max_bitrate_; 42 | std::uint64_t min_bitrate_; 43 | double avg_bitrate_; 44 | 45 | std::uint32_t metric_samples_; 46 | std::uint64_t bitrate_total_; 47 | 48 | std::int64_t max_object_time_delta_; 49 | std::int64_t min_object_time_delta_; 50 | double avg_object_time_delta_; 51 | std::int64_t total_time_delta_; 52 | 53 | std::int64_t max_object_arrival_delta_; 54 | std::int64_t min_object_arrival_delta_; 55 | double avg_object_arrival_delta_; 56 | std::int64_t total_arrival_delta_; 57 | }; 58 | 59 | class PerfSubClient : public quicr::Client 60 | { 61 | public: 62 | PerfSubClient(const quicr::ClientConfig& cfg, const std::string& configfile, std::uint32_t test_identifier); 63 | void StatusChanged(Status status) override; 64 | void MetricsSampled(const quicr::ConnectionMetrics&) override {} 65 | 66 | bool HandlersComplete(); 67 | void Terminate(); 68 | 69 | private: 70 | bool terminate_; 71 | std::string configfile_; 72 | ini::IniFile inif_; 73 | std::uint32_t test_identifier_; 74 | 75 | std::vector> track_handlers_; 76 | 77 | std::mutex track_handlers_mutex_; 78 | }; 79 | 80 | } // namespace -------------------------------------------------------------------------------- /cmd/qperf2/run_parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NUM_SUBS=${1:-100} # Arg 1 4 | 5 | echo "Running $NUM_SUBS subscriber clients" 6 | 7 | parallel -j ${NUM_SUBS} "./qperf_sub -i {} --connect_uri moq://localhost:33435 > t_{}logs.txt 2>&1" ::: $(seq ${NUM_SUBS}) -------------------------------------------------------------------------------- /dependencies/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | # ---------------------------------------------------- 5 | # Command overrides 6 | # ---------------------------------------------------- 7 | 8 | # Override install command to add FRAMEWORK DESTINATION if missing. This is needed for framework (apple) builds 9 | if(CMAKE_FRAMEWORK) 10 | macro(install) 11 | set (list_args "${ARGN}") 12 | if ("TARGETS" IN_LIST list_args AND NOT "FRAMEWORK" IN_LIST list_args) 13 | list(APPEND list_args "FRAMEWORK" "DESTINATION" "framework" "BUNDLE" "DESTINATION" "bundle") 14 | message(STATUS "Adding FRAMEWORK DESTINATION to install: ${list_args}") 15 | _install(${list_args}) 16 | else () 17 | message(STATUS "unchanged install: ${list_args}") 18 | _install(${ARGN}) 19 | endif () 20 | endmacro() 21 | endif() 22 | 23 | # -------------------------------------------------------- 24 | # Submodules 25 | # -------------------------------------------------------- 26 | 27 | if (NOT TARGET spdlog) 28 | add_subdirectory(spdlog) 29 | endif() 30 | 31 | if(BUILD_TESTING AND QUICR_BUILD_TESTS AND NOT TARGET doctest) 32 | add_subdirectory(doctest) 33 | endif() 34 | 35 | set(BUILD_SHARED_LIBS OFF) 36 | set(BUILD_STATIC_LIBS ON) 37 | 38 | # mbedtls 39 | if (${USE_MBEDTLS}) 40 | message("Transport building with MBedTLS") 41 | 42 | if (NOT TARGET mbedcrypto) # If not already available on build server 43 | # Setup python environment for mbedtls to find. 44 | find_package(Python3 REQUIRED) 45 | execute_process( 46 | COMMAND 47 | ${Python3_EXECUTABLE} -m venv ${CMAKE_CURRENT_BINARY_DIR}/venv 48 | RESULT_VARIABLE 49 | MBEDTLS_VIRTUALENV 50 | ) 51 | if (MBEDTLS_VIRTUALENV) 52 | message(FATAL_ERROR "Failed to create mbedtls virtual envrionment") 53 | endif (MBEDTLS_VIRTUALENV) 54 | 55 | # Override python lookup to use virtualenv when mbedtls later searches for it. 56 | set(Python3_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/venv/bin/python) 57 | 58 | # Install mbedtls dependencies into virtualenv. 59 | execute_process( 60 | COMMAND 61 | ${Python3_EXECUTABLE} -m pip install -r ${CMAKE_CURRENT_SOURCE_DIR}/mbedtls/scripts/basic.requirements.txt 62 | RESULT_VARIABLE 63 | MBEDTLS_PIP 64 | ) 65 | if (MBEDTLS_PIP) 66 | message(FATAL_ERROR "Failed to install mbedtls dependencies") 67 | endif (MBEDTLS_PIP) 68 | 69 | # Continue with mbedtls inclusion. 70 | option(ENABLE_TESTING OFF) 71 | option(ENABLE_PROGRAMS OFF) 72 | set(MBEDTLS_AS_SUBPROJECT ON) 73 | add_subdirectory(mbedtls) 74 | 75 | set(MBEDTLS_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mbedtls) 76 | set(MBEDTLS_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/mbedtls ${CMAKE_CURRENT_SOURCE_DIR}/mbedtls/include) 77 | 78 | elseif(PLATFORM_ESP_IDF) 79 | set(MBEDTLS_ROOT_DIR ${IDF_PATH}/components/mbedtls/mbedtls) 80 | set(MBEDTLS_INCLUDE_DIRS ${IDF_PATH}/components/mbedtls/mbedtls ${IDF_PATH}/components/mbedtls/mbedtls/include ${IDF_PATH}/components/mbedtls/port/include) 81 | endif() 82 | 83 | set(MBEDTLS_LIBRARIES mbedtls mbedcrypto mbedx509) 84 | set(MBEDTLS_LIBRARY mbedtls) 85 | set(MBEDTLS_CRYPTO mbedcrypto) 86 | set(MBEDTLS_X509 mbedx509) 87 | 88 | 89 | # picoTLS 90 | # picoTLS uses its own find mbedtls, which works with the above settings. 91 | set(WITH_MBEDTLS ON) 92 | option(WITH_FUSION OFF) 93 | set(PICOTLS_USE_BROTLI OFF) 94 | set(picotls_BUILD_TESTS OFF) 95 | add_subdirectory(picotls) 96 | add_dependencies(picotls-mbedtls mbedtls) 97 | 98 | # picoQUIC 99 | # Picoquic uses its own find mbedtls, which works with a prefix and will not work if mbedtls include dirs is set 100 | if(NOT PLATFORM_ESP_IDF) 101 | unset(MBEDTLS_INCLUDE_DIRS) 102 | endif() 103 | set(MbedTLS_FOUND ON) 104 | if (PLATFORM_ESP_IDF) 105 | set(MBEDTLS_PREFIX ${IDF_PATH}/components/mbedtls/mbedtls) 106 | else() 107 | set(MBEDTLS_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/mbedtls) 108 | endif() 109 | option(WITH_MBEDTLS ON) 110 | 111 | set(OPENSSL_INCLUDE_DIR "") # Picoquic will still error if this is not set 112 | set(PICOQUIC_PTLS_SUBMODULE ON) 113 | option(WITH_OPENSSL OFF) 114 | option(BUILD_DEMO OFF) 115 | option(BUILD_HTTP OFF) 116 | option(BUILD_LOGREADER OFF) # Disable picolog_t build 117 | set(picoquic_BUILD_TESTS OFF) 118 | set(PICOQUIC_ADDITIONAL_C_FLAGS -Wno-error=format) 119 | set(PICOQUIC_ADDITIONAL_CXX_FLAGS -Wno-error=format) 120 | add_subdirectory(picoquic) 121 | add_dependencies(picoquic-core picotls-core mbedtls) 122 | else () 123 | option(WITH_FUSION OFF) 124 | OPTION(ENABLE_TESTING OFF) 125 | 126 | # picoTLS 127 | set(WITH_OPENSSL ON) 128 | set(PICOTLS_USE_BROTLI OFF) 129 | set(picotls_BUILD_TESTS OFF) 130 | add_subdirectory(picotls) 131 | 132 | # picoQUIC 133 | set(PICOQUIC_PTLS_SUBMODULE ON) 134 | option(WITH_OPENSSL ON) 135 | set(picoquic_BUILD_TESTS OFF) 136 | add_subdirectory(picoquic) 137 | add_dependencies(picoquic-core picotls-core) 138 | 139 | endif () 140 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | The work in this directory and sub directories under this directory (c) 2 | 2024 by Cisco is licensed under Creative Commons Attribution 4.0 3 | International. To view a copy of this license, visit 4 | https://creativecommons.org/licenses/by/4.0/ 5 | -------------------------------------------------------------------------------- /docs/api-main.md: -------------------------------------------------------------------------------- 1 | # Media over QUIC (MoQ) Publisher/Subscriber API 2 | 3 | This library provides a publisher and subscriber API using MoQ. It provides 4 | a class for [Client](#quicr::Client) and [Server](#quicr::Server) that 5 | will handle all MoQ protocol state machine functions. Both classes contain virtual methods (e.g., callbacks) 6 | that **SHOULD** be implemented. 7 | 8 | Subscriptions and publications are handled via [Subscribe Track Handler](#quicr::SubscribeTrackHandler) 9 | and [Publish Track Handler](#quicr::PublishTrackHandler). Both classes contain virtual methods 10 | (e.g., callbacks) that **SHOULD** be implemented. 11 | 12 | ## Client 13 | 14 | Class | Description 15 | ------------------------------|----------------------------------------------------------- 16 | quicr::Client | Client handler, which is specific to a QUIC IP connection 17 | quicr::ClientConfig | Client configuration 18 | 19 | ## Server 20 | 21 | Class | Description 22 | ------------------------------|------------------------------------------------------------------------ 23 | quicr::Server | Server handler, which is specific to the QUIC IP listening IP and port 24 | quicr::ServerConfig | Server configuration 25 | 26 | ## Track Handlers 27 | 28 | Both client and server provide quicr::Transport::PublishTrack() and quicr::Transport::SubscribeTrack() 29 | methods to start a new subscription and/or publication. Use the below handler classes when calling 30 | the methods. Each track handler is constructed for a single full track name (e.g., namespace and name). 31 | 32 | Class | Description 33 | --------------------------------|------------------------------------------------------------------------ 34 | quicr::SubscribeTrackHandler | Subscribe track handler for subscribe related operations and callbacks 35 | quicr::PublishTrackHandler | Publish track handler for publish related operations and callbacks 36 | 37 | 38 | See [API Guide](api-guide.html) for more details on the API. See [Examples](examples.html) for detailed example of how to get started 39 | using the APIs. 40 | 41 | @example server.cpp 42 | @example client.cpp 43 | -------------------------------------------------------------------------------- /docs/css/doxygen-awesome-sidebar-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 - 2023 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* side nav width. MUST be = `TREEVIEW_WIDTH`. 32 | * Make sure it is wide enough to contain the page title (logo + title + version) 33 | */ 34 | --side-nav-fixed-width: 335px; 35 | --menu-display: none; 36 | 37 | --top-height: 120px; 38 | --toc-sticky-top: -25px; 39 | --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px); 40 | } 41 | 42 | #projectname { 43 | white-space: nowrap; 44 | } 45 | 46 | 47 | @media screen and (min-width: 768px) { 48 | html { 49 | --searchbar-background: var(--page-background-color); 50 | } 51 | 52 | #side-nav { 53 | min-width: var(--side-nav-fixed-width); 54 | max-width: var(--side-nav-fixed-width); 55 | top: var(--top-height); 56 | overflow: visible; 57 | } 58 | 59 | #nav-tree, #side-nav { 60 | height: calc(100vh - var(--top-height)) !important; 61 | } 62 | 63 | #nav-tree { 64 | padding: 0; 65 | } 66 | 67 | #top { 68 | display: block; 69 | border-bottom: none; 70 | height: var(--top-height); 71 | margin-bottom: calc(0px - var(--top-height)); 72 | max-width: var(--side-nav-fixed-width); 73 | overflow: hidden; 74 | background: var(--side-nav-background); 75 | } 76 | #main-nav { 77 | float: left; 78 | padding-right: 0; 79 | } 80 | 81 | .ui-resizable-handle { 82 | cursor: default; 83 | width: 1px !important; 84 | background: var(--separator-color); 85 | box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color); 86 | } 87 | 88 | #nav-path { 89 | position: fixed; 90 | right: 0; 91 | left: var(--side-nav-fixed-width); 92 | bottom: 0; 93 | width: auto; 94 | } 95 | 96 | #doc-content { 97 | height: calc(100vh - 31px) !important; 98 | padding-bottom: calc(3 * var(--spacing-large)); 99 | padding-top: calc(var(--top-height) - 80px); 100 | box-sizing: border-box; 101 | margin-left: var(--side-nav-fixed-width) !important; 102 | } 103 | 104 | #MSearchBox { 105 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium))); 106 | } 107 | 108 | #MSearchField { 109 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px); 110 | } 111 | 112 | #MSearchResultsWindow { 113 | left: var(--spacing-medium) !important; 114 | right: auto; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /docs/images/client-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quicr/libquicr/c4ff949cb3ec5f5863033a6956cc19765e901fcb/docs/images/client-api.png -------------------------------------------------------------------------------- /docs/pandoc-theme/elegant_bootstrap_menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | $for(author-meta)$ 48 | 49 | $endfor$ 50 | $if(date-meta)$ 51 | 52 | $endif$ 53 | $if(title-prefix)$$title-prefix$ - $endif$$pagetitle$ 54 | 55 | $if(quotes)$ 56 | 57 | $endif$ 58 | $if(highlighting-css)$ 59 | 62 | $endif$ 63 | $for(css)$ 64 | 65 | $endfor$ 66 | $if(math)$ 67 | $math$ 68 | $endif$ 69 | $for(header-includes)$ 70 | $header-includes$ 71 | $endfor$ 72 | 73 | 74 | 75 | 76 | $if(title)$ 77 | 87 | $endif$ 88 |
89 |
90 | $if(toc)$ 91 |
92 |
93 | 94 | $toc$ 95 | 96 |
97 |
98 | $endif$ 99 |
100 | 101 | $if(abstract)$ 102 |

$abstract-title$

103 | $abstract$ 104 | $endif$ 105 | 106 | $for(include-before)$ 107 | $include-before$ 108 | $endfor$ 109 | $body$ 110 | $for(include-after)$ 111 | $include-after$ 112 | $endfor$ 113 |
114 |
115 |
116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /include/quicr/cache.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "detail/tick_service.h" 14 | 15 | namespace quicr { 16 | template 17 | class Cache 18 | { 19 | /*=======================================================================*/ 20 | // Internal type definitions 21 | /*=======================================================================*/ 22 | 23 | using TickType = TickService::TickType; 24 | using IndexType = std::uint32_t; 25 | 26 | using BucketType = std::vector; 27 | using ValueType = std::shared_ptr; 28 | using CacheType = std::map; 29 | 30 | public: 31 | Cache(size_t duration, size_t interval, std::shared_ptr tick_service) 32 | : duration_{ duration } 33 | , interval_{ interval } 34 | , total_buckets_{ duration_ / interval_ } 35 | , tick_service_(std::move(tick_service)) 36 | { 37 | if (duration == 0 || duration % interval != 0 || duration == interval) { 38 | throw std::invalid_argument("Invalid time_queue constructor args"); 39 | } 40 | 41 | if (!tick_service_) { 42 | throw std::invalid_argument("Tick service cannot be null"); 43 | } 44 | 45 | buckets_.resize(total_buckets_); 46 | } 47 | 48 | Cache() = delete; 49 | Cache(const Cache&) = default; 50 | Cache(Cache&&) noexcept = default; 51 | 52 | Cache& operator=(const Cache&) = default; 53 | Cache& operator=(Cache&&) noexcept = default; 54 | 55 | size_t Size() const noexcept { return cache_.size(); } 56 | bool Empty() const noexcept { return cache_.empty(); } 57 | 58 | void Insert(const K& key, const T& value, size_t ttl) { InternalInsert(key, value, ttl); } 59 | 60 | void Insert(const K& key, T&& value, size_t ttl) { InternalInsert(key, std::move(value), ttl); } 61 | 62 | bool Contains(const K& key) noexcept 63 | { 64 | Advance(); 65 | return cache_.find(key) != cache_.end(); 66 | } 67 | 68 | bool Contains(const K& start_key, const K& end_key) 69 | { 70 | if (start_key >= end_key) { 71 | throw std::invalid_argument("Exclusive end key must be greater than start key"); 72 | } 73 | 74 | Advance(); 75 | 76 | for (auto key = start_key; key < end_key; ++key) { 77 | if (cache_.find(key) == cache_.end()) { 78 | return false; 79 | } 80 | } 81 | 82 | return true; 83 | } 84 | 85 | ValueType Get(const K& key) noexcept 86 | { 87 | if (!Contains(key)) { 88 | return nullptr; 89 | } 90 | 91 | return cache_.at(key); 92 | } 93 | 94 | std::vector Get(const K& start_key, const K& end_key) 95 | { 96 | 97 | if (!Contains(start_key, end_key)) { 98 | return {}; 99 | } 100 | 101 | std::vector entries(end_key - start_key, nullptr); 102 | for (auto key = start_key; key < end_key; ++key) { 103 | entries[key - start_key] = cache_.at(key); 104 | } 105 | 106 | return entries; 107 | } 108 | 109 | ValueType First() noexcept 110 | { 111 | Advance(); 112 | 113 | if (cache_.empty()) { 114 | return nullptr; 115 | } 116 | 117 | return std::begin(cache_)->second; 118 | } 119 | 120 | ValueType Last() noexcept 121 | { 122 | Advance(); 123 | 124 | if (cache_.empty()) { 125 | return nullptr; 126 | } 127 | 128 | return std::prev(std::end(cache_))->second; 129 | } 130 | 131 | void Clear() noexcept 132 | { 133 | cache_.clear(); 134 | bucket_index_ = 0; 135 | 136 | for (auto& bucket : buckets_) { 137 | bucket.clear(); 138 | } 139 | } 140 | 141 | protected: 142 | inline void Advance() 143 | { 144 | const TickType new_ticks = tick_service_->Milliseconds(); 145 | const TickType delta = current_ticks_ ? (new_ticks - current_ticks_) / interval_ : 0; 146 | current_ticks_ = new_ticks; 147 | 148 | if (delta == 0) { 149 | return; 150 | } 151 | 152 | if (delta >= static_cast(total_buckets_)) { 153 | Clear(); 154 | return; 155 | } 156 | 157 | for (TickType i = 0; i < delta; ++i) { 158 | auto& bucket = buckets_[(bucket_index_ + i) % total_buckets_]; 159 | for (const auto& key : bucket) { 160 | cache_.erase(key); 161 | } 162 | bucket.clear(); 163 | } 164 | 165 | bucket_index_ = (bucket_index_ + delta) % total_buckets_; 166 | } 167 | 168 | template 169 | inline void InternalInsert(const K& key, Value value, size_t ttl) 170 | { 171 | if (ttl > duration_) { 172 | throw std::invalid_argument("TTL is greater than max duration"); 173 | } else if (ttl == 0) { 174 | ttl = duration_; 175 | } 176 | 177 | ttl /= interval_; 178 | 179 | Advance(); 180 | const IndexType future_index = (bucket_index_ + ttl - 1) % total_buckets_; 181 | 182 | buckets_[future_index].push_back(key); 183 | cache_[key] = std::make_shared(value); 184 | } 185 | 186 | protected: 187 | /// The duration in ticks of the entire queue. 188 | const size_t duration_; 189 | 190 | /// The interval at which buckets are cleared in ticks. 191 | const size_t interval_; 192 | 193 | /// The total amount of buckets. Value is calculated by duration / interval. 194 | const size_t total_buckets_; 195 | 196 | /// The index in time of the current bucket. 197 | IndexType bucket_index_{ 0 }; 198 | 199 | /// Last calculated tick value. 200 | TickType current_ticks_{ 0 }; 201 | 202 | /// The memory storage for all keys to be managed. 203 | std::vector buckets_; 204 | 205 | /// The cache of elements being stored. 206 | CacheType cache_; 207 | 208 | /// Tick service for calculating new tick and jumps in time. 209 | std::shared_ptr tick_service_; 210 | }; 211 | 212 | } // namespace quicr 213 | -------------------------------------------------------------------------------- /include/quicr/common.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include "detail/quic_transport.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace quicr { 12 | 13 | constexpr uint64_t kMoqtVersion = 0xff000008; ///< draft-ietf-quicr-transport-08 14 | constexpr uint64_t kSubscribeExpires = 0; ///< Never expires 15 | constexpr int kReadLoopMaxPerStream = 60; ///< Support packet/frame bursts, but do not allow starving other streams 16 | 17 | using namespace quicr; 18 | 19 | using Byte = uint8_t; 20 | using Bytes = std::vector; 21 | using BytesSpan = std::span; 22 | using ConnectionHandle = uint64_t; 23 | /** 24 | * @brief Publish announce attributes 25 | * 26 | * @details Various attributes relative to the publish announce 27 | */ 28 | struct PublishAnnounceAttributes 29 | { 30 | uint64_t request_id{ 0 }; 31 | }; 32 | 33 | /** 34 | * @brief Client Setup Attributes 35 | */ 36 | struct ClientSetupAttributes 37 | { 38 | const std::string endpoint_id; 39 | }; 40 | 41 | /** 42 | * @brief Server Setup Attributes 43 | */ 44 | struct ServerSetupAttributes 45 | { 46 | const uint64_t moqt_version; 47 | const std::string server_id; 48 | }; 49 | 50 | /** 51 | * @brief Publish Announce Status 52 | */ 53 | enum class PublishAnnounceStatus : uint8_t 54 | { 55 | kOK = 0, 56 | kNotConnected, 57 | kNotAnnounced, 58 | kPendingAnnounceResponse, 59 | kAnnounceNotAuthorized, 60 | kSendingUnannounce, ///< In this state, callbacks will not be called 61 | }; 62 | } 63 | // namespace quicr 64 | -------------------------------------------------------------------------------- /include/quicr/config.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include "quicr/version.h" 7 | #include 8 | #include 9 | 10 | namespace quicr { 11 | 12 | struct Config 13 | { 14 | std::string endpoint_id; ///< Endpoint ID for the client or server, should be unique 15 | ///< working to add to protocol: https://github.com/moq-wg/moq-transport/issues/461 16 | 17 | quicr::TransportConfig transport_config; 18 | uint64_t metrics_sample_ms{ 5000 }; 19 | }; 20 | 21 | struct ClientConfig : Config 22 | { 23 | std::string connect_uri; ///< URI such as moqt://relay[:port][/path?query] 24 | std::uint64_t tick_service_sleep_delay_us{ 333 }; 25 | }; 26 | 27 | struct ServerConfig : Config 28 | { 29 | 30 | std::string server_bind_ip; ///< IP address to bind to, can be 0.0.0.0 or :: 31 | ///< Empty will be treated as ANY 32 | uint16_t server_port; ///< Listening port for server 33 | std::uint64_t tick_service_sleep_delay_us{ 333 }; 34 | }; 35 | 36 | } // namespace moq 37 | -------------------------------------------------------------------------------- /include/quicr/detail/base_track_handler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include "quicr/common.h" 7 | #include "quicr/detail/ctrl_message_types.h" 8 | #include "quicr/track_name.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace quicr { 15 | 16 | /** 17 | * @brief Track mode object of object published or received 18 | * 19 | * @details QUIC stream handling mode used to send objects or how object was received 20 | */ 21 | enum class TrackMode : uint8_t 22 | { 23 | kDatagram, 24 | kStream, 25 | }; 26 | 27 | /** 28 | * @brief Response to received MOQT Subscribe message 29 | */ 30 | struct SubscribeResponse 31 | { 32 | /** 33 | * @details **kOK** indicates that the subscribe is accepted and OK should be sent. Any other 34 | * value indicates that the subscribe is not accepted and the reason code and other 35 | * fields will be set. 36 | */ 37 | enum class ReasonCode : uint8_t 38 | { 39 | kOk = 0, 40 | kInternalError, 41 | kInvalidRange, 42 | kRetryTrackAlias, 43 | }; 44 | ReasonCode reason_code; 45 | 46 | std::optional error_reason = std::nullopt; 47 | std::optional track_alias = std::nullopt; ///< Set only when ResponseCode is kRetryTrackAlias 48 | 49 | std::optional largest_location = std::nullopt; 50 | }; 51 | 52 | /** 53 | * @brief MoQ track base handler for tracks (subscribe/publish) 54 | * 55 | * @details Base MoQ track handler 56 | */ 57 | class BaseTrackHandler 58 | { 59 | public: 60 | friend class Transport; 61 | friend class Server; 62 | 63 | virtual ~BaseTrackHandler() = default; 64 | 65 | // -------------------------------------------------------------------------- 66 | // Public API methods that normally should not be overridden 67 | // -------------------------------------------------------------------------- 68 | 69 | BaseTrackHandler() = delete; 70 | 71 | protected: 72 | /** 73 | * @brief Track delegate constructor 74 | * 75 | * @param full_track_name Full track name struct 76 | */ 77 | BaseTrackHandler(const FullTrackName& full_track_name) 78 | : full_track_name_(full_track_name) 79 | { 80 | } 81 | 82 | // -------------------------------------------------------------------------- 83 | // Public Virtual API callback event methods to be overridden 84 | // -------------------------------------------------------------------------- 85 | public: 86 | /** 87 | * @brief Set the track alias 88 | * @details MOQ transport instance will set the track alias when the track has 89 | * been assigned. 90 | * 91 | * @param track_alias MoQ track alias for track namespace+name that 92 | * is relative to the QUIC connection session 93 | */ 94 | void SetTrackAlias(uint64_t track_alias) { full_track_name_.track_alias = track_alias; } 95 | 96 | /** 97 | * @brief Get the track alias 98 | * @returns Track alias as an optional. Track alias may not be set yet. If not 99 | * set, nullopt will be returned. 100 | */ 101 | std::optional GetTrackAlias() const noexcept { return full_track_name_.track_alias; } 102 | 103 | /** 104 | * @brief Sets the reqeust ID 105 | * @details MoQ instance sets the request id based on subscribe track method call. Request 106 | * id is specific to the connection, so it must be set by the moq instance/connection. 107 | * 108 | * @param request_id 62bit request ID 109 | */ 110 | void SetRequestId(std::optional request_id) { request_id_ = request_id; } 111 | 112 | /** 113 | * @brief Get the request ID 114 | * 115 | * @return nullopt if not subscribed, otherwise the request ID 116 | */ 117 | std::optional GetRequestId() const noexcept { return request_id_; } 118 | 119 | /** 120 | * @brief Get the full track name 121 | * 122 | * @details Gets the full track name 123 | * 124 | * @return FullTrackName 125 | */ 126 | FullTrackName GetFullTrackName() const noexcept { return { full_track_name_ }; } 127 | 128 | /** 129 | * @brief Get the connection ID 130 | */ 131 | uint64_t GetConnectionId() const noexcept { return connection_handle_; }; 132 | 133 | // -------------------------------------------------------------------------- 134 | // Internal 135 | // -------------------------------------------------------------------------- 136 | private: 137 | /** 138 | * @brief Set the connection ID 139 | * 140 | * @details The MOQ Handler sets the connection ID 141 | */ 142 | void SetConnectionId(uint64_t connection_handle) { connection_handle_ = connection_handle; }; 143 | 144 | // -------------------------------------------------------------------------- 145 | // Member variables 146 | // -------------------------------------------------------------------------- 147 | 148 | FullTrackName full_track_name_; 149 | 150 | ConnectionHandle connection_handle_; // QUIC transport connection ID 151 | 152 | /** 153 | * request_id_ is the primary index/key for subscribe context/delegate storage. 154 | * It is use as the request_id in MoQ related subscribes. Request ID will adapt 155 | * to received reqeust IDs, so the value will reflect either the received reqeust ID 156 | * or the next one that increments from last received ID. 157 | */ 158 | std::optional request_id_; 159 | }; 160 | 161 | } // namespace moq 162 | -------------------------------------------------------------------------------- /include/quicr/detail/ctrl_message_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "quicr/common.h" 3 | #include "quicr/detail/uintvar.h" 4 | 5 | namespace quicr::messages { 6 | quicr::Bytes& operator<<(quicr::Bytes& buffer, const quicr::Bytes& bytes); 7 | 8 | quicr::Bytes& operator<<(quicr::Bytes& buffer, const quicr::BytesSpan& bytes); 9 | quicr::BytesSpan operator>>(quicr::BytesSpan buffer, quicr::Bytes& value); 10 | 11 | quicr::Bytes& operator<<(quicr::Bytes& buffer, std::uint64_t value); 12 | quicr::BytesSpan operator>>(quicr::BytesSpan buffer, std::uint64_t& value); 13 | 14 | quicr::Bytes& operator<<(quicr::Bytes& buffer, std::uint8_t value); 15 | quicr::BytesSpan operator>>(quicr::BytesSpan buffer, uint8_t& value); 16 | 17 | Bytes& operator<<(Bytes& buffer, std::uint16_t value); 18 | BytesSpan operator>>(BytesSpan buffer, std::uint16_t& value); 19 | 20 | quicr::Bytes& operator<<(quicr::Bytes& buffer, const quicr::UintVar& value); 21 | 22 | using GroupId = uint64_t; 23 | using ObjectId = uint64_t; 24 | // TODO(RichLogan): Remove when ErrorReason -> ReasonPhrase. 25 | using ReasonPhrase = Bytes; 26 | 27 | struct ControlMessage 28 | { 29 | std::uint64_t type{ 0 }; 30 | Bytes payload{}; 31 | }; 32 | Bytes& operator<<(Bytes& buffer, const ControlMessage& message); 33 | BytesSpan operator>>(BytesSpan buffer, ControlMessage& message); 34 | 35 | struct Location 36 | { 37 | GroupId group{ 0 }; 38 | ObjectId object{ 0 }; 39 | }; 40 | Bytes& operator<<(Bytes& buffer, const Location& location); 41 | BytesSpan operator>>(BytesSpan buffer, Location& location); 42 | 43 | /// MoQ Key Value Pair. 44 | template 45 | concept KeyType = 46 | std::same_as || (std::is_enum_v && std::same_as, std::uint64_t>); 47 | template 48 | struct KeyValuePair 49 | { 50 | T type; 51 | Bytes value; 52 | }; 53 | template 54 | Bytes& operator<<(Bytes& buffer, const KeyValuePair& param) 55 | { 56 | const auto type = static_cast(param.type); 57 | buffer << UintVar(type); 58 | if (type % 2 == 0) { 59 | // Even, single varint of value. 60 | assert(param.value.size() <= 8); 61 | std::uint64_t val = 0; 62 | std::memcpy(&val, param.value.data(), std::min(param.value.size(), sizeof(std::uint64_t))); 63 | buffer << UintVar(val); 64 | } else { 65 | // Odd, encode bytes. 66 | buffer << UintVar(param.value.size()); 67 | buffer.insert(buffer.end(), param.value.begin(), param.value.end()); 68 | } 69 | return buffer; 70 | } 71 | template 72 | BytesSpan operator>>(BytesSpan buffer, KeyValuePair& param) 73 | { 74 | std::uint64_t type; 75 | buffer = buffer >> type; 76 | param.type = static_cast(type); 77 | if (type % 2 == 0) { 78 | // Even, single varint of value. 79 | UintVar uvar(buffer); 80 | buffer = buffer.subspan(uvar.size()); 81 | std::uint64_t val(uvar); 82 | param.value.resize(uvar.size()); 83 | std::memcpy(param.value.data(), &val, uvar.size()); 84 | } else { 85 | // Odd, decode bytes. 86 | uint64_t size = 0; 87 | buffer = buffer >> size; 88 | param.value.assign(buffer.begin(), std::next(buffer.begin(), size)); 89 | buffer = buffer.subspan(size); 90 | } 91 | return buffer; 92 | } 93 | 94 | // Serialization for all uint64_t/enum(uint64_t to varint). 95 | template 96 | Bytes& operator<<(Bytes& buffer, const T value) 97 | { 98 | buffer << UintVar(static_cast(value)); 99 | return buffer; 100 | } 101 | template 102 | BytesSpan operator>>(BytesSpan buffer, T& value) 103 | { 104 | std::uint64_t uvalue; 105 | buffer = buffer >> uvalue; 106 | value = static_cast(uvalue); 107 | return buffer; 108 | } 109 | 110 | enum struct ParameterType : uint64_t 111 | { 112 | kPath = 0x1, 113 | kMaxRequestId = 0x2, // version specific, unused 114 | kEndpointId = 0xF1, // Endpoint ID, using temp value for now 115 | kInvalid = 0xFF, // used internally. 116 | }; 117 | 118 | using Parameter = KeyValuePair; 119 | 120 | enum struct GroupOrder : uint8_t 121 | { 122 | kOriginalPublisherOrder = 0x0, 123 | kAscending, 124 | kDescending 125 | }; 126 | 127 | Bytes& operator<<(Bytes& buffer, GroupOrder value); 128 | BytesSpan operator>>(BytesSpan buffer, GroupOrder& value); 129 | 130 | enum struct FilterType : uint64_t 131 | { 132 | kNone = 0x0, 133 | kLatestGroup, 134 | kLatestObject, 135 | kAbsoluteStart, 136 | kAbsoluteRange 137 | }; 138 | 139 | enum class TrackStatusCode : uint64_t 140 | { 141 | kInProgress = 0x00, 142 | kDoesNotExist, 143 | kNotStarted, 144 | kFinished, 145 | kUnknown 146 | }; 147 | 148 | enum class SubscribeDoneStatusCode : uint64_t 149 | { 150 | kInternalError = 0x00, 151 | kUnauthorized, 152 | kTrackEnded, 153 | kSubscribtionEnded, 154 | kGoingAway, 155 | kExpired, 156 | kTooFarBehind, 157 | }; 158 | 159 | enum class FetchType : uint8_t 160 | { 161 | kStandalone = 0x1, 162 | kJoiningFetch, 163 | }; 164 | 165 | Bytes& operator<<(Bytes& buffer, FetchType value); 166 | BytesSpan operator>>(BytesSpan buffer, FetchType& value); 167 | 168 | enum class TerminationReason : uint64_t 169 | { 170 | kNoError = 0x0, 171 | kInternalError, 172 | kUnauthorized, 173 | kProtocolViolation, 174 | kDupTrackAlias, 175 | kParamLengthMismatch, 176 | kGoAwayTimeout = 0x10, 177 | }; 178 | 179 | enum class FetchErrorCode : uint8_t 180 | { 181 | kInternalError = 0x0, 182 | kUnauthorized = 0x1, 183 | kTimeout = 0x2, 184 | kNotSupported = 0x3, 185 | kTrackDoesNotExist = 0x4, 186 | kInvalidRange = 0x5, 187 | }; 188 | 189 | Bytes& operator<<(Bytes& buffer, FetchErrorCode value); 190 | BytesSpan operator>>(BytesSpan buffer, FetchErrorCode& value); 191 | 192 | enum class AnnounceErrorCode : uint64_t 193 | { 194 | kInternalError = 0x0, 195 | kUnauthorized, 196 | kTimeout, 197 | kNotSupported, 198 | kUninterested 199 | }; 200 | 201 | // TODO (Suhas): rename it to StreamMapping 202 | enum ForwardingPreference : uint8_t 203 | { 204 | kStreamPerGroup = 0, 205 | kStreamPerObject, 206 | kStreamPerPriority, 207 | kStreamPerTrack, 208 | kDatagram 209 | }; 210 | 211 | Bytes& operator<<(Bytes& buffer, ForwardingPreference value); 212 | BytesSpan operator>>(BytesSpan buffer, ForwardingPreference& value); 213 | 214 | enum class SubscribeErrorCode : uint64_t 215 | { 216 | kInternalError = 0x0, 217 | kUnauthorized, 218 | kTimeout, 219 | kNotSupported, 220 | kTrackDoesNotExist, 221 | kInvalidRange, 222 | kRetryTrackAlias, 223 | 224 | kTrackNotExist = 0xF0 // Missing in draft 225 | }; 226 | 227 | enum class SubscribeAnnouncesErrorCode : uint64_t 228 | { 229 | kInternalError = 0x0, 230 | kUnauthorized, 231 | kTimeout, 232 | kNotSupported, 233 | kNamespacePrefixUnknown, 234 | }; 235 | } // namespace 236 | -------------------------------------------------------------------------------- /include/quicr/detail/defer.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #define DEFER_CONCAT(a, b) DEFER_CONCAT_INNER(a, b) 10 | #define DEFER_CONCAT_INNER(a, b) a##b 11 | #define defer(n) quicr::DeferType DEFER_CONCAT(defer_, __COUNTER__)([&] { n; }) 12 | 13 | namespace quicr { 14 | class DeferType 15 | { 16 | public: 17 | explicit DeferType(std::function&& f) 18 | : func{ f } 19 | { 20 | } 21 | ~DeferType() { func(); } 22 | 23 | private: 24 | std::function func; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /include/quicr/detail/joining_fetch_handler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace quicr { 10 | /** 11 | * JoiningFetchHandler is used internally in order to forward JOINING FETCH 12 | * streams to their corresponding SUBSCRIBE track handler, for convenience. 13 | */ 14 | class JoiningFetchHandler : public SubscribeTrackHandler 15 | { 16 | public: 17 | explicit JoiningFetchHandler(std::shared_ptr joining_subscribe) 18 | : SubscribeTrackHandler(joining_subscribe->GetFullTrackName(), 19 | joining_subscribe->GetPriority(), 20 | joining_subscribe->GetGroupOrder(), 21 | joining_subscribe->GetFilterType()) 22 | , joining_subscribe_(std::move(joining_subscribe)) 23 | { 24 | } 25 | void StreamDataRecv(bool is_start, 26 | uint64_t stream_id, 27 | std::shared_ptr> data) override; 28 | 29 | private: 30 | std::shared_ptr joining_subscribe_; 31 | }; 32 | 33 | } // namespace moq 34 | -------------------------------------------------------------------------------- /include/quicr/detail/safe_queue.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace quicr { 15 | 16 | /** 17 | * @brief safe_queue is a thread safe basic queue 18 | * 19 | * @details This class is a thread safe wrapper for std::queue. 20 | * Not all operators or methods are implemented. 21 | * 22 | * @todo Implement any operators or methods needed 23 | */ 24 | template 25 | class SafeQueue 26 | { 27 | public: 28 | /** 29 | * @brief safe_queue constructor 30 | * 31 | * @param limit Limit number of messages in queue before push blocks. Zero 32 | * is unlimited. 33 | */ 34 | SafeQueue(uint32_t limit = 5'000) 35 | : stop_waiting_{ false } 36 | , limit_{ limit } 37 | { 38 | } 39 | 40 | ~SafeQueue() { StopWaiting(); } 41 | 42 | /** 43 | * @brief inserts element at the end of queue 44 | * 45 | * @details Inserts element at the end of queue. If queue is at max size, 46 | * the front element will be popped/removed to make room. 47 | * In this sense, the queue is sliding forward with every new message 48 | * added to queue. 49 | * 50 | * @param elem The element to insert. 51 | * @return True if successfully pushed, false if not. The cause for false is 52 | * that the queue is full. 53 | */ 54 | bool Push(T const& elem) 55 | { 56 | bool rval = true; 57 | 58 | std::lock_guard _(mutex_); 59 | 60 | if (queue_.empty()) { 61 | cv_.notify_one(); 62 | empty_ = false; 63 | } 64 | 65 | else if (queue_.size() >= limit_) { // Make room by removing first element 66 | queue_.pop(); 67 | rval = false; 68 | } 69 | 70 | queue_.push(elem); 71 | 72 | return rval; 73 | } 74 | 75 | /** 76 | * @brief Remove the first object from queue (oldest object) 77 | * 78 | * @return std::nullopt if queue is empty, otherwise reference to object 79 | */ 80 | std::optional Pop() 81 | { 82 | std::lock_guard _(mutex_); 83 | return PopInternal(); 84 | } 85 | 86 | /** 87 | * @brief Get first object without removing from queue 88 | * 89 | * @return std::nullopt if queue is empty, otherwise reference to object 90 | */ 91 | std::optional Front() 92 | { 93 | std::lock_guard _(mutex_); 94 | 95 | if (queue_.empty()) { 96 | return std::nullopt; 97 | } 98 | 99 | return queue_.front(); 100 | } 101 | 102 | /** 103 | * @brief Remove (aka pop) the first object from queue 104 | * 105 | */ 106 | void PopFront() 107 | { 108 | std::lock_guard _(mutex_); 109 | 110 | PopFrontInternal(); 111 | } 112 | 113 | /** 114 | * @brief Block waiting for data in queue, then remove the first object from 115 | * queue (oldest object) 116 | * 117 | * @details This will block if the queue is empty. Due to concurrency, it's 118 | * possible that when unblocked the queue might still be empty. In this case, 119 | * try again. 120 | * 121 | * @return std::nullopt if queue is empty, otherwise reference to object 122 | */ 123 | std::optional BlockPop() 124 | { 125 | std::unique_lock lock(mutex_); 126 | cv_.wait(lock, [&]() { return (stop_waiting_ || (queue_.size() > 0)); }); 127 | 128 | if (stop_waiting_) { 129 | return std::nullopt; 130 | } 131 | 132 | return PopInternal(); 133 | } 134 | 135 | /** 136 | * @brief Size of the queue 137 | * 138 | * @return size of the queue 139 | */ 140 | size_t Size() 141 | { 142 | std::lock_guard _(mutex_); 143 | return queue_.size(); 144 | } 145 | 146 | /** 147 | * @brief Clear the queue 148 | */ 149 | void Clear() 150 | { 151 | std::lock_guard _(mutex_); 152 | std::queue empty; 153 | std::swap(queue_, empty); 154 | } 155 | 156 | /** 157 | * @brief Check if queue is empty 158 | * 159 | * @returns True if empty, false if not 160 | */ 161 | bool Empty() const { return empty_; } 162 | 163 | /** 164 | * @brief Put the queue in a state such that threads will not wait 165 | */ 166 | void StopWaiting() 167 | { 168 | std::lock_guard _(mutex_); 169 | stop_waiting_ = true; 170 | cv_.notify_all(); 171 | } 172 | 173 | void SetLimit(uint32_t limit) 174 | { 175 | std::lock_guard _(mutex_); 176 | limit_ = limit; 177 | } 178 | 179 | private: 180 | /** 181 | * @brief Remove the first object from queue (oldest object) 182 | * 183 | * @return std::nullopt if queue is empty, otherwise reference to object 184 | * 185 | * @details The mutex must be locked by the caller 186 | */ 187 | std::optional PopInternal() 188 | { 189 | if (queue_.empty()) { 190 | empty_ = true; 191 | return std::nullopt; 192 | } 193 | 194 | auto elem = queue_.front(); 195 | queue_.pop(); 196 | 197 | if (queue_.empty()) { 198 | empty_ = true; 199 | } 200 | 201 | return elem; 202 | } 203 | 204 | /** 205 | * @brief Remove the first object from queue (oldest object) 206 | * 207 | * @details The mutex must be locked by the caller 208 | */ 209 | void PopFrontInternal() 210 | { 211 | if (queue_.empty()) { 212 | empty_ = true; 213 | return; 214 | } 215 | 216 | queue_.pop(); 217 | 218 | if (queue_.empty()) { 219 | empty_ = true; 220 | } 221 | } 222 | 223 | std::atomic empty_{ true }; 224 | bool stop_waiting_; // Instruct threads to stop waiting 225 | uint32_t limit_; // Limit of number of messages in queue 226 | std::condition_variable cv_; // Signaling for thread syncronization 227 | std::mutex mutex_; // read/write lock 228 | std::queue queue_; // Queue 229 | }; 230 | 231 | } /* namespace quicr */ 232 | -------------------------------------------------------------------------------- /include/quicr/detail/tick_service.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2023 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | /** 5 | * time_queue.h 6 | * 7 | * Description: 8 | * A time based queue, where the length of the queue is a duration, 9 | * divided into buckets based on a given time interval. As time 10 | * progresses, buckets in the past are cleared, and the main queue 11 | * is updated so that the front only returns a valid object that 12 | * has not expired. To improve performance, buckets are only cleared 13 | * on push or pop operations. Thus, buckets in the past can be 14 | * cleared in bulk based on how many we should have advanced since 15 | * the last time we updated. 16 | * 17 | * Portability Issues: 18 | * None. 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace quicr { 30 | 31 | #ifdef ESP_PLATFORM 32 | #define SET_THREAD_STACKSIZE(stack_size) \ 33 | pthread_attr_t attr; \ 34 | pthread_attr_init(&attr); \ 35 | pthread_attr_setstacksize(&attr, stack_size) 36 | #else 37 | #define SET_THREAD_STACKSIZE(stack_size) 38 | #endif 39 | 40 | /** 41 | * Interface for services that calculate ticks. 42 | */ 43 | struct TickService 44 | { 45 | using TickType = size_t; 46 | using DurationType = std::chrono::microseconds; 47 | 48 | virtual TickType Milliseconds() const = 0; 49 | virtual TickType Microseconds() const = 0; 50 | virtual ~TickService() = default; 51 | }; 52 | 53 | /** 54 | * @brief Calculates elapsed time in ticks. 55 | * 56 | * @details Calculates time that's elapsed between update calls. Keeps 57 | * track of time using ticks as a counter of elapsed time. The 58 | * precision 500us or greater, which results in the tick interval 59 | * being >= 500us. 60 | */ 61 | class ThreadedTickService : public TickService 62 | { 63 | using ClockType = std::chrono::steady_clock; 64 | 65 | public: 66 | ThreadedTickService(std::uint64_t sleep_delay_us = 333) 67 | : sleep_delay_us_{ sleep_delay_us } 68 | { 69 | SET_THREAD_STACKSIZE(1024); 70 | tick_thread_ = std::thread(&ThreadedTickService::TickLoop, this); 71 | } 72 | 73 | ThreadedTickService(const ThreadedTickService& other) 74 | : ticks_{ other.ticks_ } 75 | , sleep_delay_us_{ other.sleep_delay_us_ } 76 | , stop_{ other.stop_.load() } 77 | { 78 | SET_THREAD_STACKSIZE(1024); 79 | tick_thread_ = std::thread(&ThreadedTickService::TickLoop, this); 80 | } 81 | 82 | ~ThreadedTickService() override 83 | { 84 | stop_ = true; 85 | if (tick_thread_.joinable()) 86 | tick_thread_.join(); 87 | } 88 | 89 | ThreadedTickService& operator=(const ThreadedTickService& other) 90 | { 91 | ticks_ = other.ticks_; 92 | sleep_delay_us_ = other.sleep_delay_us_; 93 | stop_ = other.stop_.load(); 94 | 95 | SET_THREAD_STACKSIZE(1024); 96 | tick_thread_ = std::thread(&ThreadedTickService::TickLoop, this); 97 | return *this; 98 | } 99 | 100 | TickType Microseconds() const override { return ticks_; } 101 | 102 | TickType Milliseconds() const override { return ticks_ / 1000; } 103 | 104 | private: 105 | void TickLoop() 106 | { 107 | auto prev_time = ClockType::now(); 108 | 109 | while (!stop_) { 110 | const auto& now = ClockType::now(); 111 | const uint64_t delta = std::chrono::duration_cast(now - prev_time).count(); 112 | std::this_thread::sleep_for(std::chrono::microseconds(sleep_delay_us_)); 113 | 114 | if (delta >= sleep_delay_us_) { 115 | ticks_ += delta; 116 | prev_time = now; 117 | } 118 | } 119 | } 120 | 121 | private: 122 | /// The current ticks since the tick_service began. 123 | uint64_t ticks_{ 0 }; 124 | 125 | /// Sleep delay in microseconds 126 | uint64_t sleep_delay_us_; 127 | 128 | /// Flag to stop tick_service thread. 129 | std::atomic stop_{ false }; 130 | 131 | /// The thread to update ticks on. 132 | std::thread tick_thread_; 133 | }; 134 | 135 | }; // namespace quicr 136 | -------------------------------------------------------------------------------- /include/quicr/detail/uintvar.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace quicr { 14 | namespace { 15 | constexpr std::uint16_t SwapBytes(const std::uint16_t value) 16 | { 17 | if constexpr (std::endian::native == std::endian::big) 18 | return value; 19 | 20 | return ((value >> 8) & 0x00FF) | ((value << 8) & 0xFF00); 21 | } 22 | 23 | constexpr std::uint32_t SwapBytes(const std::uint32_t value) 24 | { 25 | if constexpr (std::endian::native == std::endian::big) 26 | return value; 27 | 28 | return ((value >> 24) & 0x000000FF) | ((value >> 8) & 0x0000FF00) | ((value << 8) & 0x00FF0000) | 29 | ((value << 24) & 0xFF000000); 30 | } 31 | 32 | constexpr std::uint64_t SwapBytes(const std::uint64_t value) 33 | { 34 | if constexpr (std::endian::native == std::endian::big) 35 | return value; 36 | 37 | return ((value >> 56) & 0x00000000000000FF) | ((value >> 40) & 0x000000000000FF00) | 38 | ((value >> 24) & 0x0000000000FF0000) | ((value >> 8) & 0x00000000FF000000) | 39 | ((value << 8) & 0x000000FF00000000) | ((value << 24) & 0x0000FF0000000000) | 40 | ((value << 40) & 0x00FF000000000000) | ((value << 56) & 0xFF00000000000000); 41 | } 42 | } 43 | 44 | class UintVar 45 | { 46 | public: 47 | constexpr UintVar(uint64_t value) 48 | : be_value_{ std::bit_cast>(SwapBytes(value)) } 49 | { 50 | constexpr uint64_t k14bitLength = (static_cast(-1) << (64 - 6) >> (64 - 6)); 51 | constexpr uint64_t k30bitLength = (static_cast(-1) << (64 - 14) >> (64 - 14)); 52 | constexpr uint64_t k62bitLength = (static_cast(-1) << (64 - 30) >> (64 - 30)); 53 | 54 | if (be_value_.front() & 0xC0u) { // Check if invalid 55 | throw std::invalid_argument("Value greater than uintvar maximum"); 56 | } 57 | 58 | std::uint64_t be_v = std::bit_cast(be_value_); 59 | if (value > k62bitLength) { // 62 bit encoding (8 bytes) 60 | be_v |= 0xC0ull; 61 | } else if (value > k30bitLength) { // 30 bit encoding (4 bytes) 62 | be_v >>= 32; 63 | be_v |= 0x80ull; 64 | } else if (value > k14bitLength) { // 14 bit encoding (2 bytes) 65 | be_v >>= 48; 66 | be_v |= 0x40ull; 67 | } else { 68 | be_v >>= 56; 69 | } 70 | 71 | be_value_ = std::bit_cast>(be_v); 72 | } 73 | 74 | constexpr UintVar(std::span bytes) 75 | : be_value_{ 0 } 76 | { 77 | if (bytes.empty() || bytes.size() < Size(bytes.front())) { 78 | throw std::invalid_argument("Invalid bytes for uintvar"); 79 | } 80 | 81 | const std::size_t size = Size(bytes.front()); 82 | if (std::is_constant_evaluated()) { 83 | for (std::size_t i = 0; i < size; ++i) { 84 | be_value_[i] = bytes.data()[i]; 85 | } 86 | } else { 87 | std::memcpy(&be_value_, bytes.data(), size); 88 | } 89 | } 90 | 91 | constexpr UintVar(const UintVar&) noexcept = default; 92 | constexpr UintVar(UintVar&&) noexcept = default; 93 | constexpr UintVar& operator=(const UintVar&) noexcept = default; 94 | constexpr UintVar& operator=(UintVar&&) noexcept = default; 95 | 96 | constexpr UintVar& operator=(std::uint64_t value) 97 | { 98 | UintVar t(value); 99 | this->be_value_ = t.be_value_; 100 | return *this; 101 | } 102 | 103 | constexpr std::uint64_t Get() const noexcept 104 | { 105 | return SwapBytes((std::bit_cast(be_value_) & SwapBytes(uint64_t(~(~0x3Full << 56)))) 106 | << (sizeof(uint64_t) - Size()) * 8); 107 | } 108 | 109 | static constexpr std::size_t Size(uint8_t msb_bytes) noexcept 110 | { 111 | if ((msb_bytes & 0xC0) == 0xC0) { 112 | return sizeof(uint64_t); 113 | } else if ((msb_bytes & 0x80) == 0x80) { 114 | return sizeof(uint32_t); 115 | } else if ((msb_bytes & 0x40) == 0x40) { 116 | return sizeof(uint16_t); 117 | } 118 | 119 | return sizeof(uint8_t); 120 | } 121 | 122 | constexpr std::size_t Size() const noexcept { return UintVar::Size(be_value_.front()); } 123 | 124 | // NOLINTBEGIN(readability-identifier-naming) 125 | constexpr const std::uint8_t* data() const noexcept { return be_value_.data(); } 126 | constexpr std::size_t size() const noexcept { return Size(); } 127 | constexpr auto begin() const noexcept { return data(); } 128 | constexpr auto end() const noexcept { return data() + Size(); } 129 | // NOLINTEND(readability-identifier-naming) 130 | 131 | explicit constexpr operator uint64_t() const noexcept { return Get(); } 132 | 133 | constexpr auto operator<=>(const UintVar&) const noexcept = default; 134 | 135 | private: 136 | std::array be_value_; 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /include/quicr/fetch_track_handler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace quicr { 11 | class FetchTrackHandler : public SubscribeTrackHandler 12 | { 13 | protected: 14 | /** 15 | * @brief Fetch track handler constructor 16 | * 17 | * @param full_track_name Full track name struct. 18 | * @param priority The priority of the track. 19 | * @param group_order The group order to use. 20 | * @param start_group The starting group of the range. 21 | * @param end_group The final group in the range. 22 | * @param start_object The starting object in a group. 23 | * @param end_object The final object in a group. 24 | */ 25 | FetchTrackHandler(const FullTrackName& full_track_name, 26 | messages::SubscriberPriority priority, 27 | messages::GroupOrder group_order, 28 | messages::GroupId start_group, 29 | messages::GroupId end_group, 30 | messages::GroupId start_object, 31 | messages::GroupId end_object) 32 | : SubscribeTrackHandler(full_track_name, priority, group_order, messages::FilterType::kLatestGroup) 33 | , start_group_(start_group) 34 | , start_object_(start_object) 35 | , end_group_(end_group) 36 | , end_object_(end_object) 37 | { 38 | } 39 | 40 | public: 41 | /** 42 | * @brief Create shared Fetch track handler. 43 | * 44 | * @param full_track_name Full track name struct. 45 | * @param priority The priority of the track. 46 | * @param group_order The group order to use. 47 | * @param start_group The starting group of the range. 48 | * @param start_object The starting object in a group. 49 | * @param end_group The final group in the range. 50 | * @param end_object The final object in a group. 51 | * 52 | * @returns Shared pointer to a Fetch track handler. 53 | */ 54 | static std::shared_ptr Create(const FullTrackName& full_track_name, 55 | messages::SubscriberPriority priority, 56 | messages::GroupOrder group_order, 57 | messages::GroupId start_group, 58 | messages::GroupId end_group, 59 | messages::GroupId start_object, 60 | messages::GroupId end_object) 61 | { 62 | return std::shared_ptr(new FetchTrackHandler( 63 | full_track_name, priority, group_order, start_group, end_group, start_object, end_object)); 64 | } 65 | 66 | /** 67 | * @brief Get the starting group id of the Fetch range. 68 | * @returns The starting group ID. 69 | */ 70 | constexpr const messages::GroupId& GetStartGroup() const noexcept { return start_group_; } 71 | 72 | /** 73 | * @brief Get the id of the group one past the end of the Fetch range. 74 | * @returns The ending group ID. 75 | */ 76 | constexpr const messages::GroupId& GetEndGroup() const noexcept { return end_group_; } 77 | 78 | /** 79 | * @brief Get the starting object id of the Group range. 80 | * @returns The starting object ID. 81 | */ 82 | constexpr const messages::GroupId& GetStartObject() const noexcept { return start_object_; } 83 | 84 | /** 85 | * @brief Get the id of the object one past the end of the group range. 86 | * @returns The ending object ID. 87 | */ 88 | constexpr const messages::GroupId& GetEndObject() const noexcept { return end_object_; } 89 | 90 | void StreamDataRecv(bool is_start, 91 | uint64_t stream_id, 92 | std::shared_ptr> data) override; 93 | 94 | private: 95 | messages::GroupId start_group_; 96 | messages::GroupId start_object_; 97 | messages::GroupId end_group_; 98 | messages::GroupId end_object_; 99 | 100 | friend class Transport; 101 | friend class Client; 102 | friend class Server; 103 | }; 104 | 105 | } // namespace moq 106 | -------------------------------------------------------------------------------- /include/quicr/hash.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace quicr { 12 | /// The generated CRC-64 table. 13 | static constexpr std::array crc_table = [] { 14 | std::array table; 15 | for (std::uint64_t c = 0; c < 256; ++c) { 16 | std::uint64_t crc = c; 17 | for (std::uint64_t i = 0; i < 8; ++i) { 18 | std::uint64_t b = (crc & 1); 19 | crc >>= 1; 20 | crc ^= (0 - b) & 0xc96c5795d7870f42ull; 21 | } 22 | table[c] = crc; 23 | } 24 | 25 | return table; 26 | }(); 27 | 28 | /** 29 | * @brief Compute CRC-64-ECMA hash of string. 30 | * 31 | * @param bytes Bytes to hash 32 | * @returns The hash of the given bytes. 33 | */ 34 | static constexpr std::uint64_t hash(const std::span bytes) 35 | { 36 | 37 | constexpr size_t word_len = sizeof(std::uint64_t); 38 | std::uint64_t crc = 0; 39 | 40 | if (bytes.size() <= word_len) { 41 | std::memcpy(&crc, bytes.data(), bytes.size()); 42 | return crc; 43 | } 44 | 45 | /* 46 | const auto word_count = bytes.size() / word_len; 47 | 48 | auto start_it = bytes.begin(); 49 | for (size_t i = 0; i < word_count; ++i) { 50 | uint64_t* word = (uint64_t*)&*start_it; 51 | 52 | crc = crc_table[(crc ^ *word) & 0xFF] ^ (crc >> 8); 53 | 54 | start_it += word_len; 55 | } 56 | 57 | for (size_t i = word_count * word_len; i < bytes.size(); i++) { 58 | crc = crc_table[(crc & 0xFF) ^ bytes[i]] ^ (crc >> 8); 59 | } 60 | */ 61 | 62 | for (const auto& b : bytes) { 63 | crc = crc_table[(crc & 0xFF) ^ b] ^ (crc >> 8); 64 | } 65 | 66 | return crc; 67 | } 68 | 69 | /** 70 | * @brief Compute CRC-64-ECMA hash of string. 71 | * 72 | * @param str The string to hash 73 | * 74 | * @returns The hash of the given string. 75 | */ 76 | [[maybe_unused]] 77 | static std::uint64_t hash(const std::string_view& str) 78 | { 79 | return hash(std::vector{ str.begin(), str.end() }); 80 | } 81 | 82 | /** 83 | * @brief Combine (aka add) hash to existing hash 84 | * 85 | * @details Adds/combines new hash to existing hash. Existing hash will 86 | * be updated. 87 | * 88 | * @param[in,out] existing_hash Existing hash to update 89 | * @param[in] add_hash New hash to add to the existing (combine) 90 | */ 91 | inline void hash_combine(uint64_t& existing_hash, const uint64_t& add_hash) 92 | { 93 | existing_hash ^= add_hash + 0x9e3779b9 + (existing_hash << 6) + (add_hash >> 2); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /include/quicr/metrics.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include "detail/quic_transport_metrics.h" 7 | #include 8 | 9 | namespace quicr { 10 | using namespace quicr; 11 | using MetricsTimeStampUs = uint64_t; ///< Metrics timestamp in microseconds from epoch 1970 12 | 13 | struct ConnectionMetrics 14 | { 15 | MetricsTimeStampUs last_sample_time; ///< Last sampled time in microseconds 16 | 17 | QuicConnectionMetrics quic; ///< QUIC connection metrics 18 | 19 | uint64_t rx_dgram_unknown_track_alias{ 0 }; ///< Received datagram with unknown track alias 20 | uint64_t rx_dgram_invalid_type{ 0 }; ///< Received datagram with invalid type of kObjectDatagram 21 | uint64_t rx_dgram_decode_failed{ 0 }; ///< Failed to decode datagram 22 | 23 | uint64_t rx_stream_buffer_error{ 0 }; ///< Stream buffer error that results in bad parsing 24 | uint64_t rx_stream_unknown_track_alias{ 0 }; ///< Received stream header with unknown track alias 25 | uint64_t rx_stream_invalid_type{ 0 }; ///< Invalid message type 26 | 27 | uint64_t invalid_ctrl_stream_msg{ 0 }; ///< Invalid control stream message received. Should always be 0. 28 | }; 29 | 30 | struct SubscribeTrackMetrics 31 | { 32 | MetricsTimeStampUs last_sample_time; ///< Last sampled time in microseconds 33 | 34 | uint64_t bytes_received{ 0 }; ///< sum of payload bytes received 35 | uint64_t objects_received{ 0 }; ///< count of objects received 36 | }; 37 | 38 | struct PublishTrackMetrics 39 | { 40 | MetricsTimeStampUs last_sample_time; ///< Last sampled time in microseconds 41 | 42 | uint64_t bytes_published{ 0 }; ///< sum of payload bytes published 43 | uint64_t objects_published{ 0 }; ///< count of objects published 44 | 45 | uint64_t objects_dropped_not_ok{ 0 }; ///< Objects dropped upon publish object call due to status not being OK 46 | 47 | struct Quic 48 | { 49 | uint64_t tx_buffer_drops{ 0 }; ///< count of write buffer drops of data due to RESET request 50 | uint64_t tx_queue_discards{ 0 }; ///< count of objects discarded due clear and transition to new stream 51 | uint64_t tx_queue_expired{ 0 }; ///< count of objects expired before pop/front due to TTL expiry 52 | 53 | uint64_t tx_delayed_callback{ 0 }; ///< count of times transmit callbacks were delayed 54 | uint64_t tx_reset_wait{ 0 }; ///< count of times data context performed a reset and wait 55 | 56 | MinMaxAvg tx_queue_size; ///< TX queue size in period 57 | MinMaxAvg tx_callback_ms; ///< Callback time in milliseconds in period 58 | MinMaxAvg tx_object_duration_us; ///< TX object time in queue duration in microseconds 59 | } quic; 60 | }; 61 | 62 | } // namespace moq 63 | -------------------------------------------------------------------------------- /include/quicr/object.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace quicr { 12 | using Extensions = std::map>; 13 | 14 | /** 15 | * @brief Status of object as reported by the publisher 16 | */ 17 | enum struct ObjectStatus : uint8_t 18 | { 19 | kAvailable = 0x0, 20 | kDoesNotExist = 0x1, 21 | kEndOfGroup = 0x3, 22 | kEndOfTrack = 0x4, 23 | kEndOfSubGroup = 0x5 24 | }; 25 | 26 | /** 27 | * @brief Object headers struct 28 | * 29 | * @details Object headers are passed when sending and receiving an object. The object headers describe the object. 30 | */ 31 | struct ObjectHeaders 32 | { 33 | uint64_t group_id; ///< Object group ID - Application defined order of generation 34 | uint64_t object_id; ///< Object ID - Application defined order of generation 35 | uint64_t subgroup_id{ 0 }; ///< Subgroup ID - Starts at 0, monotonically increases by 1 36 | uint64_t payload_length; ///< Length of payload of the object data 37 | ObjectStatus status; ///< Status of the object at the publisher 38 | std::optional priority; ///< Priority of the object, lower value is better 39 | std::optional ttl; ///< Object time to live in milliseconds 40 | std::optional track_mode; ///< Track Mode of how the object was received or mode to use when sending 41 | std::optional extensions; 42 | }; 43 | 44 | } 45 | // namespace moq 46 | -------------------------------------------------------------------------------- /include/quicr/publish_fetch_handler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace quicr { 10 | class PublishFetchHandler : public PublishTrackHandler 11 | { 12 | protected: 13 | PublishFetchHandler(const FullTrackName& full_track_name, 14 | uint8_t priority, 15 | uint64_t subscribe_id, 16 | messages::GroupOrder group_order, 17 | uint32_t ttl) 18 | : PublishTrackHandler(full_track_name, TrackMode::kStream, priority, ttl) 19 | , group_order_(group_order) 20 | { 21 | SetRequestId(subscribe_id); 22 | } 23 | 24 | public: 25 | static std::shared_ptr Create(const FullTrackName& full_track_name, 26 | uint8_t priority, 27 | uint64_t subscribe_id, 28 | messages::GroupOrder group_order, 29 | uint32_t ttl) 30 | { 31 | return std::shared_ptr( 32 | new PublishFetchHandler(full_track_name, priority, subscribe_id, group_order, ttl)); 33 | } 34 | PublishObjectStatus PublishObject(const ObjectHeaders& object_headers, BytesSpan data) override; 35 | constexpr messages::GroupOrder GetGroupOrder() const noexcept { return group_order_; } 36 | 37 | private: 38 | messages::GroupOrder group_order_; 39 | bool sent_first_header_{ false }; 40 | }; 41 | 42 | } // namespace moq 43 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | if (${QUICR_BUILD_SHARED}) 5 | add_library(quicr SHARED) 6 | else() 7 | add_library(quicr) 8 | endif() 9 | target_sources (quicr PRIVATE 10 | client.cpp 11 | ctrl_message_types.cpp 12 | ctrl_messages.cpp 13 | messages.cpp 14 | publish_fetch_handler.cpp 15 | publish_track_handler.cpp 16 | fetch_track_handler.cpp 17 | subscribe_track_handler.cpp 18 | server.cpp 19 | quic_transport.cpp 20 | transport.cpp 21 | transport_picoquic.cpp 22 | joining_fetch_handler.cpp 23 | ) 24 | 25 | target_include_directories(quicr PUBLIC ${CMAKE_BINARY_DIR}/include ) 26 | 27 | # Suppress external lib warnings 28 | set_property(GLOBAL PROPERTY RULE_MESSAGES OFF) 29 | 30 | target_compile_options(quicr 31 | PRIVATE 32 | $<$,$,$>: -Wpedantic -Wextra -Wall> 33 | $<$: >) 34 | set_target_properties(quicr 35 | PROPERTIES 36 | CXX_STANDARD 20 37 | CXX_STANDARD_REQUIRED YES 38 | CXX_EXTENSIONS OFF) 39 | 40 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 41 | target_compile_options(quicr PRIVATE -Wall -pedantic -Wextra -Wmissing-declarations) 42 | endif() 43 | 44 | if(MSVC) 45 | target_compile_options(quicr PRIVATE /W4 /WX) 46 | target_compile_definitions(quicr _CRT_SECURE_NO_WARNINGS) 47 | endif() 48 | 49 | target_compile_definitions(quicr PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) 50 | 51 | if(LINT) 52 | include(Lint) 53 | lint(quicr) 54 | endif() 55 | 56 | target_link_libraries(quicr 57 | PUBLIC 58 | spdlog) 59 | 60 | target_link_libraries(quicr 61 | PUBLIC 62 | picoquic-core picoquic-log) 63 | 64 | if (PLATFORM_ESP_IDF) 65 | add_compile_definitions(${LIB_NAME} PLATFORM_ESP) 66 | endif() 67 | 68 | target_include_directories(quicr PUBLIC ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src) 69 | 70 | -------------------------------------------------------------------------------- /src/ctrl_message_types.cpp: -------------------------------------------------------------------------------- 1 | #include "quicr/detail/ctrl_message_types.h" 2 | 3 | namespace quicr::messages { 4 | 5 | Bytes& operator<<(Bytes& buffer, const Bytes& bytes) 6 | { 7 | buffer << static_cast(bytes.size()); // length of byte span 8 | buffer.insert(buffer.end(), bytes.begin(), bytes.end()); 9 | return buffer; 10 | } 11 | 12 | Bytes& operator<<(Bytes& buffer, const BytesSpan& bytes) 13 | { 14 | buffer << static_cast(bytes.size()); // length of byte span 15 | buffer.insert(buffer.end(), bytes.begin(), bytes.end()); 16 | return buffer; 17 | } 18 | 19 | Bytes& operator<<(Bytes& buffer, const UintVar& varint) 20 | { 21 | buffer.insert(buffer.end(), varint.begin(), varint.end()); 22 | return buffer; 23 | } 24 | 25 | Bytes& operator<<(Bytes& buffer, std::uint8_t value) 26 | { 27 | // assign 8 bits - not a varint 28 | buffer.push_back(value); 29 | return buffer; 30 | } 31 | 32 | Bytes& operator<<(Bytes& buffer, std::uint16_t value) 33 | { 34 | const std::uint16_t swapped = SwapBytes(value); 35 | buffer.push_back(static_cast(swapped >> 8 & 0xFF)); 36 | buffer.push_back(static_cast(swapped & 0xFF)); 37 | return buffer; 38 | } 39 | 40 | Bytes& operator<<(Bytes& buffer, std::uint64_t value) 41 | { 42 | UintVar varint = value; 43 | buffer << varint; 44 | return buffer; 45 | } 46 | 47 | BytesSpan operator>>(BytesSpan buffer, Bytes& value) 48 | { 49 | uint64_t size = 0; 50 | buffer = buffer >> size; 51 | value.assign(buffer.begin(), std::next(buffer.begin(), size)); 52 | return buffer.subspan(value.size()); 53 | } 54 | 55 | BytesSpan operator>>(BytesSpan buffer, uint8_t& value) 56 | { 57 | // need 8 bits - not a varint 58 | value = buffer.front(); 59 | return buffer.subspan(sizeof(value)); 60 | } 61 | 62 | BytesSpan operator>>(BytesSpan buffer, uint16_t& value) 63 | { 64 | if (buffer.size() < sizeof(value)) { 65 | throw std::invalid_argument("Provider buffer too small"); 66 | } 67 | const std::uint16_t high = buffer[0]; 68 | const std::uint16_t low = buffer[1]; 69 | value = high << 8 | low; 70 | value = SwapBytes(value); 71 | return buffer.subspan(sizeof(std::uint16_t)); 72 | } 73 | 74 | BytesSpan operator>>(BytesSpan buffer, uint64_t& value) 75 | { 76 | UintVar value_uv(buffer); 77 | value = static_cast(value_uv); 78 | return buffer.subspan(value_uv.size()); 79 | } 80 | 81 | Bytes& operator<<(Bytes& buffer, GroupOrder value) 82 | { 83 | buffer << static_cast(value); 84 | return buffer; 85 | } 86 | 87 | BytesSpan operator>>(BytesSpan buffer, GroupOrder& value) 88 | { 89 | std::uint64_t uvalue; 90 | buffer = buffer >> uvalue; 91 | value = static_cast(uvalue); 92 | return buffer; 93 | } 94 | 95 | Bytes& operator<<(Bytes& buffer, FetchType value) 96 | { 97 | buffer << static_cast(value); 98 | return buffer; 99 | } 100 | 101 | BytesSpan operator>>(BytesSpan buffer, FetchType& value) 102 | { 103 | std::uint8_t uvalue; 104 | buffer = buffer >> uvalue; 105 | value = static_cast(uvalue); 106 | return buffer; 107 | } 108 | 109 | Bytes& operator<<(Bytes& buffer, FetchErrorCode value) 110 | { 111 | buffer << static_cast(value); 112 | return buffer; 113 | } 114 | 115 | BytesSpan operator>>(BytesSpan buffer, FetchErrorCode& value) 116 | { 117 | std::uint64_t uvalue; 118 | buffer = buffer >> uvalue; 119 | value = static_cast(uvalue); 120 | return buffer; 121 | } 122 | 123 | Bytes& operator<<(Bytes& buffer, SubscribeDoneStatusCode value) 124 | { 125 | buffer << static_cast(value); 126 | return buffer; 127 | } 128 | 129 | BytesSpan operator>>(BytesSpan buffer, SubscribeDoneStatusCode& value) 130 | { 131 | std::uint64_t uvalue; 132 | buffer = buffer >> uvalue; 133 | value = static_cast(uvalue); 134 | return buffer; 135 | } 136 | 137 | Bytes& operator<<(Bytes& buffer, SubscribeErrorCode value) 138 | { 139 | buffer << static_cast(value); 140 | return buffer; 141 | } 142 | 143 | BytesSpan operator>>(BytesSpan buffer, SubscribeErrorCode& value) 144 | { 145 | std::uint64_t uvalue; 146 | buffer = buffer >> uvalue; 147 | value = static_cast(uvalue); 148 | return buffer; 149 | } 150 | 151 | Bytes& operator<<(Bytes& buffer, const ControlMessage& message) 152 | { 153 | buffer << message.type; 154 | buffer << static_cast(message.payload.size()); 155 | buffer.insert(buffer.end(), message.payload.begin(), message.payload.end()); 156 | return buffer; 157 | } 158 | 159 | BytesSpan operator>>(BytesSpan buffer, ControlMessage& message) 160 | { 161 | buffer = buffer >> message.type; 162 | if (buffer.size() < sizeof(std::uint16_t)) { 163 | throw std::invalid_argument("Buffer too small"); 164 | } 165 | std::uint16_t payload_length; 166 | buffer = buffer >> payload_length; 167 | if (buffer.size() < payload_length) { 168 | throw std::invalid_argument("Buffer too small"); 169 | } 170 | message.payload.assign(buffer.begin(), std::next(buffer.begin(), payload_length)); 171 | return buffer.subspan(payload_length); 172 | } 173 | 174 | Bytes& operator<<(Bytes& buffer, const Location& location) 175 | { 176 | buffer << UintVar(location.group); 177 | buffer << UintVar(location.object); 178 | return buffer; 179 | } 180 | 181 | BytesSpan operator>>(BytesSpan buffer, Location& location) 182 | { 183 | buffer = buffer >> location.group; 184 | buffer = buffer >> location.object; 185 | return buffer; 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/fetch_track_handler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "quicr/fetch_track_handler.h" 5 | 6 | namespace quicr { 7 | void FetchTrackHandler::StreamDataRecv(bool is_start, 8 | [[maybe_unused]] uint64_t stream_id, 9 | std::shared_ptr> data) 10 | { 11 | if (is_start) { 12 | stream_buffer_.Clear(); 13 | 14 | stream_buffer_.InitAny(); 15 | stream_buffer_.Push(*data); 16 | stream_buffer_.Pop(); // Remove type header 17 | 18 | // Expect that on initial start of stream, there is enough data to process the stream headers 19 | 20 | auto& f_hdr = stream_buffer_.GetAny(); 21 | if (not(stream_buffer_ >> f_hdr)) { 22 | SPDLOG_ERROR("Not enough data to process new stream headers, stream is invalid"); 23 | // TODO: Add metrics to track this 24 | return; 25 | } 26 | } else { 27 | stream_buffer_.Push(*data); 28 | } 29 | 30 | if (not stream_buffer_.AnyHasValueB()) { 31 | stream_buffer_.InitAnyB(); 32 | } 33 | 34 | auto& obj = stream_buffer_.GetAnyB(); 35 | 36 | if (stream_buffer_ >> obj) { 37 | SPDLOG_TRACE("Received fetch_object subscribe_id: {} priority: {} " 38 | "group_id: {} subgroup_id: {} object_id: {} data size: {}", 39 | *GetSubscribeId(), 40 | obj.publisher_priority, 41 | obj.group_id, 42 | obj.subgroup_id, 43 | obj.object_id, 44 | obj.payload.size()); 45 | 46 | subscribe_track_metrics_.objects_received++; 47 | subscribe_track_metrics_.bytes_received += obj.payload.size(); 48 | 49 | ObjectReceived({ obj.group_id, 50 | obj.object_id, 51 | obj.subgroup_id, 52 | obj.payload.size(), 53 | obj.object_status, 54 | obj.publisher_priority, 55 | std::nullopt, 56 | TrackMode::kStream, 57 | obj.extensions }, 58 | obj.payload); 59 | 60 | stream_buffer_.ResetAnyB(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/joining_fetch_handler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "quicr/detail/joining_fetch_handler.h" 5 | 6 | namespace quicr { 7 | void JoiningFetchHandler::StreamDataRecv(bool is_start, 8 | [[maybe_unused]] uint64_t stream_id, 9 | std::shared_ptr> data) 10 | { 11 | if (is_start) { 12 | stream_buffer_.Clear(); 13 | 14 | stream_buffer_.InitAny(); 15 | stream_buffer_.Push(*data); 16 | stream_buffer_.Pop(); // Remove type header 17 | 18 | // Expect that on initial start of stream, there is enough data to process the stream headers 19 | 20 | auto& f_hdr = stream_buffer_.GetAny(); 21 | if (not(stream_buffer_ >> f_hdr)) { 22 | SPDLOG_ERROR("Not enough data to process new stream headers, stream is invalid"); 23 | // TODO: Add metrics to track this 24 | return; 25 | } 26 | } else { 27 | stream_buffer_.Push(*data); 28 | } 29 | 30 | stream_buffer_.InitAnyB(); 31 | auto& obj = stream_buffer_.GetAnyB(); 32 | 33 | if (stream_buffer_ >> obj) { 34 | SPDLOG_TRACE("Received fetch_object subscribe_id: {} priority: {} " 35 | "group_id: {} subgroup_id: {} object_id: {} data size: {}", 36 | *GetSubscribeId(), 37 | obj.publisher_priority, 38 | obj.group_id, 39 | obj.subgroup_id, 40 | obj.object_id, 41 | obj.payload.size()); 42 | 43 | joining_subscribe_->ObjectReceived({ obj.group_id, 44 | obj.object_id, 45 | obj.subgroup_id, 46 | obj.payload.size(), 47 | obj.object_status, 48 | obj.publisher_priority, 49 | std::nullopt, 50 | TrackMode::kStream, 51 | obj.extensions }, 52 | obj.payload); 53 | 54 | stream_buffer_.ResetAnyB(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/publish_fetch_handler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | namespace quicr { 7 | PublishTrackHandler::PublishObjectStatus PublishFetchHandler::PublishObject(const ObjectHeaders& object_headers, 8 | const BytesSpan data) 9 | { 10 | bool is_stream_header_needed{ !sent_first_header_ }; 11 | sent_first_header_ = true; 12 | if (publish_object_func_ == nullptr) { 13 | return PublishObjectStatus::kInternalError; 14 | } 15 | return publish_object_func_(object_headers.priority.has_value() ? object_headers.priority.value() 16 | : default_priority_, 17 | object_headers.ttl.has_value() ? object_headers.ttl.value() : default_ttl_, 18 | is_stream_header_needed, 19 | object_headers.group_id, 20 | object_headers.subgroup_id, 21 | object_headers.object_id, 22 | object_headers.extensions, 23 | data); 24 | } 25 | } -------------------------------------------------------------------------------- /src/publish_track_handler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | namespace quicr { 7 | void PublishTrackHandler::StatusChanged(Status) {} 8 | void PublishTrackHandler::MetricsSampled(const PublishTrackMetrics&) {} 9 | 10 | PublishTrackHandler::PublishObjectStatus PublishTrackHandler::ForwardPublishedData( 11 | bool is_new_stream, 12 | std::shared_ptr> data) 13 | { 14 | switch (publish_status_) { 15 | case Status::kOk: 16 | break; 17 | case Status::kNoSubscribers: 18 | publish_track_metrics_.objects_dropped_not_ok++; 19 | return PublishObjectStatus::kNoSubscribers; 20 | case Status::kPendingAnnounceResponse: 21 | [[fallthrough]]; 22 | case Status::kNotAnnounced: 23 | [[fallthrough]]; 24 | case Status::kNotConnected: 25 | publish_track_metrics_.objects_dropped_not_ok++; 26 | return PublishObjectStatus::kNotAnnounced; 27 | case Status::kAnnounceNotAuthorized: 28 | publish_track_metrics_.objects_dropped_not_ok++; 29 | return PublishObjectStatus::kNotAuthorized; 30 | case Status::kNewGroupRequested: 31 | [[fallthrough]]; 32 | case Status::kSubscriptionUpdated: 33 | // reset the status to ok to imply change 34 | if (!is_new_stream) { 35 | break; 36 | } 37 | publish_status_ = Status::kOk; 38 | break; 39 | default: 40 | publish_track_metrics_.objects_dropped_not_ok++; 41 | return PublishObjectStatus::kInternalError; 42 | } 43 | 44 | publish_track_metrics_.bytes_published += data->size(); 45 | 46 | if (forward_publish_data_func_ != nullptr) { 47 | return forward_publish_data_func_(default_priority_, default_ttl_, is_new_stream, data); 48 | } 49 | 50 | return PublishObjectStatus::kInternalError; 51 | } 52 | 53 | PublishTrackHandler::PublishObjectStatus PublishTrackHandler::PublishObject(const ObjectHeaders& object_headers, 54 | BytesSpan data) 55 | { 56 | bool is_stream_header_needed{ false }; 57 | 58 | // change in subgroups and groups require a new stream 59 | 60 | is_stream_header_needed = not sent_first_header_ || prev_sub_group_id_ != object_headers.subgroup_id || 61 | prev_object_group_id_ != object_headers.group_id; 62 | 63 | switch (publish_status_) { 64 | case Status::kOk: 65 | break; 66 | case Status::kNoSubscribers: 67 | publish_track_metrics_.objects_dropped_not_ok++; 68 | return PublishObjectStatus::kNoSubscribers; 69 | case Status::kPendingAnnounceResponse: 70 | [[fallthrough]]; 71 | case Status::kNotAnnounced: 72 | [[fallthrough]]; 73 | case Status::kNotConnected: 74 | publish_track_metrics_.objects_dropped_not_ok++; 75 | return PublishObjectStatus::kNotAnnounced; 76 | case Status::kAnnounceNotAuthorized: 77 | publish_track_metrics_.objects_dropped_not_ok++; 78 | return PublishObjectStatus::kNotAuthorized; 79 | case Status::kNewGroupRequested: 80 | [[fallthrough]]; 81 | case Status::kSubscriptionUpdated: 82 | // reset the status to ok to imply change 83 | if (!is_stream_header_needed) { 84 | break; 85 | } 86 | publish_status_ = Status::kOk; 87 | break; 88 | default: 89 | publish_track_metrics_.objects_dropped_not_ok++; 90 | return PublishObjectStatus::kInternalError; 91 | } 92 | 93 | if (object_headers.track_mode.has_value() && object_headers.track_mode != default_track_mode_) { 94 | SetDefaultTrackMode(*object_headers.track_mode); 95 | } 96 | 97 | sent_first_header_ = true; 98 | 99 | prev_object_group_id_ = object_headers.group_id; 100 | prev_sub_group_id_ = object_headers.subgroup_id; 101 | publish_track_metrics_.bytes_published += data.size(); 102 | publish_track_metrics_.objects_published++; 103 | 104 | if (publish_object_func_ != nullptr) { 105 | return publish_object_func_(object_headers.priority.has_value() ? object_headers.priority.value() 106 | : default_priority_, 107 | object_headers.ttl.has_value() ? object_headers.ttl.value() : default_ttl_, 108 | is_stream_header_needed, 109 | object_headers.group_id, 110 | object_headers.subgroup_id, 111 | object_headers.object_id, 112 | object_headers.extensions, 113 | data); 114 | } 115 | 116 | return PublishObjectStatus::kInternalError; 117 | } 118 | 119 | } // namespace quicr 120 | -------------------------------------------------------------------------------- /src/quic_transport.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "transport_picoquic.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace quicr { 12 | 13 | std::shared_ptr ITransport::MakeClientTransport(const TransportRemote& server, 14 | const TransportConfig& tcfg, 15 | TransportDelegate& delegate, 16 | std::shared_ptr tick_service, 17 | std::shared_ptr logger) 18 | { 19 | switch (server.proto) { 20 | case TransportProtocol::kQuic: 21 | return std::make_shared( 22 | server, tcfg, delegate, false, std::move(tick_service), std::move(logger)); 23 | default: 24 | throw std::runtime_error("make_client_transport: Protocol not implemented"); 25 | break; 26 | } 27 | 28 | return nullptr; 29 | } 30 | 31 | std::shared_ptr ITransport::MakeServerTransport(const TransportRemote& server, 32 | const TransportConfig& tcfg, 33 | TransportDelegate& delegate, 34 | std::shared_ptr tick_service, 35 | std::shared_ptr logger) 36 | { 37 | switch (server.proto) { 38 | 39 | case TransportProtocol::kQuic: 40 | return std::make_shared( 41 | server, tcfg, delegate, true, std::move(tick_service), std::move(logger)); 42 | default: 43 | throw std::runtime_error("make_server_transport: Protocol not implemented"); 44 | break; 45 | } 46 | 47 | return nullptr; 48 | } 49 | 50 | } // namespace quicr 51 | -------------------------------------------------------------------------------- /src/subscribe_track_handler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "quicr/detail/messages.h" 5 | #include "quicr/detail/stream_buffer.h" 6 | #include 7 | 8 | namespace quicr { 9 | 10 | void SubscribeTrackHandler::ObjectReceived([[maybe_unused]] const ObjectHeaders& object_headers, 11 | [[maybe_unused]] BytesSpan data) 12 | { 13 | } 14 | 15 | void SubscribeTrackHandler::StreamDataRecv(bool is_start, 16 | uint64_t stream_id, 17 | std::shared_ptr> data) 18 | { 19 | if (stream_id > current_stream_id_) { 20 | current_stream_id_ = stream_id; 21 | } else if (stream_id < current_stream_id_) { 22 | SPDLOG_DEBUG( 23 | "Old stream data received, stream_id: {} is less than {}, ignoring", stream_id, current_stream_id_); 24 | return; 25 | } 26 | 27 | if (is_start) { 28 | stream_buffer_.Clear(); 29 | 30 | stream_buffer_.InitAny(); 31 | stream_buffer_.Push(*data); 32 | 33 | // Expect that on initial start of stream, there is enough data to process the stream headers 34 | 35 | auto& s_hdr = stream_buffer_.GetAny(); 36 | if (not(stream_buffer_ >> s_hdr)) { 37 | SPDLOG_ERROR("Not enough data to process new stream headers, stream is invalid"); 38 | // TODO: Add metrics to track this 39 | return; 40 | } 41 | } else { 42 | stream_buffer_.Push(*data); 43 | } 44 | 45 | auto& s_hdr = stream_buffer_.GetAny(); 46 | 47 | if (not stream_buffer_.AnyHasValueB()) { 48 | stream_buffer_.InitAnyB(); 49 | } 50 | 51 | auto& obj = stream_buffer_.GetAnyB(); 52 | obj.serialize_extensions = TypeWillSerializeExtensions(s_hdr.type); 53 | if (stream_buffer_ >> obj) { 54 | SPDLOG_TRACE("Received stream_subgroup_object type: {} priority: {} track_alias: {} " 55 | "group_id: {} subgroup_id: {} object_id: {} data size: {}", 56 | static_cast(s_hdr.subgroup_type), 57 | s_hdr.priority, 58 | s_hdr.track_alias, 59 | s_hdr.group_id, 60 | s_hdr.subgroup_id.has_value() ? *s_hdr.subgroup_id : -1, 61 | obj.object_id, 62 | obj.payload.size()); 63 | 64 | if (!s_hdr.subgroup_id.has_value()) { 65 | // TODO(RichLogan): This is a protocol error? 66 | assert(s_hdr.type == messages::StreamHeaderType::kSubgroupFirstObjectNoExtensions || 67 | s_hdr.type == messages::StreamHeaderType::kSubgroupFirstObjectWithExtensions); 68 | s_hdr.subgroup_id = obj.object_id; 69 | } 70 | 71 | subscribe_track_metrics_.objects_received++; 72 | subscribe_track_metrics_.bytes_received += obj.payload.size(); 73 | 74 | ObjectReceived({ s_hdr.group_id, 75 | obj.object_id, 76 | s_hdr.subgroup_id.value(), 77 | obj.payload.size(), 78 | obj.object_status, 79 | s_hdr.priority, 80 | std::nullopt, 81 | TrackMode::kStream, 82 | obj.extensions }, 83 | obj.payload); 84 | 85 | stream_buffer_.ResetAnyB(); 86 | } 87 | } 88 | 89 | void SubscribeTrackHandler::DgramDataRecv(std::shared_ptr> data) 90 | { 91 | stream_buffer_.Clear(); 92 | 93 | stream_buffer_.Push(*data); 94 | stream_buffer_.Pop(); // Remove type header 95 | 96 | messages::ObjectDatagram msg; 97 | if (stream_buffer_ >> msg) { 98 | SPDLOG_TRACE("Received object datagram conn_id: {0} data_ctx_id: {1} subscriber_id: {2} " 99 | "track_alias: {3} group_id: {4} object_id: {5} data size: {6}", 100 | conn_id, 101 | (data_ctx_id ? *data_ctx_id : 0), 102 | msg.subscribe_id, 103 | msg.track_alias, 104 | msg.group_id, 105 | msg.object_id, 106 | msg.payload.size()); 107 | 108 | subscribe_track_metrics_.objects_received++; 109 | subscribe_track_metrics_.bytes_received += msg.payload.size(); 110 | ObjectReceived( 111 | { 112 | msg.group_id, 113 | msg.object_id, 114 | 0, // datagrams don't have subgroups 115 | msg.payload.size(), 116 | ObjectStatus::kAvailable, 117 | msg.priority, 118 | std::nullopt, 119 | TrackMode::kDatagram, 120 | msg.extensions, 121 | }, 122 | std::move(msg.payload)); 123 | } 124 | } 125 | 126 | void SubscribeTrackHandler::RequestNewGroup() noexcept 127 | { 128 | if (new_group_request_callback_ && GetRequestId().has_value() && GetTrackAlias().has_value()) { 129 | new_group_request_callback_(GetRequestId().value(), GetTrackAlias().value()); 130 | } 131 | } 132 | } // namespace quicr 133 | -------------------------------------------------------------------------------- /src/version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define QUICR_VERSION "@PROJECT_VERSION@" 4 | #define QUICR_NAME "@PROJECT_NAME@" 5 | #define QUICR_DESCRIPTION "@PROJECT_DESCRIPTION@" 6 | 7 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | add_executable(quicr_test 5 | main.cpp 6 | moq_data_messages.cpp 7 | moq_ctrl_messages.cpp 8 | moq_test.cpp 9 | track_handlers.cpp 10 | uintvar.cpp 11 | client.cpp 12 | tick_service.cpp 13 | track_namespace.cpp 14 | data_storage.cpp 15 | cache.cpp 16 | ) 17 | target_include_directories(quicr_test PRIVATE ${PROJECT_SOURCE_DIR}/src) 18 | 19 | target_link_libraries(quicr_test PRIVATE quicr doctest::doctest) 20 | 21 | target_compile_options(quicr_test 22 | PRIVATE 23 | $<$,$,$>: -Wpedantic -Wextra -Wall> 24 | $<$: >) 25 | 26 | set_target_properties(quicr_test 27 | PROPERTIES 28 | CXX_STANDARD 20 29 | CXX_STANDARD_REQUIRED YES 30 | CXX_EXTENSIONS ON) 31 | 32 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 33 | target_compile_options(quicr_test PRIVATE -Wall -pedantic -Wextra) 34 | endif() 35 | 36 | if(MSVC) 37 | target_compile_options(quicr_test PRIVATE /W4 /WX) 38 | target_compile_definitions(quicr_test _CRT_SECURE_NO_WARNINGS) 39 | endif() 40 | 41 | include(${CMAKE_SOURCE_DIR}/dependencies/doctest/scripts/cmake/doctest.cmake) 42 | doctest_discover_tests(quicr_test) 43 | 44 | if (LINT) 45 | include(Lint) 46 | Lint(quicr_test) 47 | endif() 48 | -------------------------------------------------------------------------------- /test/cache.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2025 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | 8 | using namespace quicr; 9 | 10 | struct MockTickService : TickService 11 | { 12 | void SetCurrentDuration(const DurationType duration) { milliseconds_ = duration; } 13 | TickType Milliseconds() const override { return milliseconds_.count(); } 14 | TickType Microseconds() const override { return milliseconds_.count() * 1000; } 15 | 16 | private: 17 | DurationType milliseconds_ = DurationType(0); 18 | }; 19 | 20 | TEST_SUITE("Cache") 21 | { 22 | TEST_CASE("Cache Retrieval") 23 | { 24 | // Should be able to find objects that have been inserted. 25 | auto time = std::make_shared(); 26 | typedef std::uint64_t Key; 27 | typedef std::vector Value; 28 | auto cache = Cache(1000, 100, std::make_shared()); 29 | constexpr Key target_key = 0; 30 | Value expected = { 0, 1 }; 31 | cache.Insert(target_key, expected, 1000); 32 | Value expected_second = { 0 }; 33 | cache.Insert(target_key + 1, expected_second, 1000); 34 | 35 | // Lookup by matching key. 36 | CHECK(cache.Contains(target_key)); 37 | // Lookup by matching intra range would throw. 38 | CHECK_THROWS_AS(cache.Contains(target_key, target_key), const std::invalid_argument&); 39 | // Lookup by matching (key+1). 40 | CHECK(cache.Contains(target_key + 1)); 41 | // Lookup by matching range. 42 | CHECK(cache.Contains(target_key, target_key + 1)); 43 | 44 | // Check throws on intra-get and backwards range. 45 | CHECK_THROWS_AS(cache.Get(target_key, target_key), const std::invalid_argument&); 46 | CHECK_THROWS_AS(cache.Get(target_key + 1, target_key), const std::invalid_argument&); 47 | 48 | // Get target key. 49 | auto retrieved = cache.Get(target_key, target_key + 1); 50 | REQUIRE(retrieved.size() == 1); 51 | CHECK(*retrieved[0] == expected); 52 | 53 | // Get both keys. 54 | retrieved = cache.Get(target_key, target_key + 2); 55 | REQUIRE(retrieved.size() == 2); 56 | CHECK(*retrieved[0] == expected); 57 | CHECK(*retrieved[1] == expected_second); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/client.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | TEST_CASE("Multiple client creation") 11 | { 12 | auto client = std::make_shared(quicr::ClientConfig()); 13 | client = nullptr; 14 | client = std::make_shared(quicr::ClientConfig()); 15 | } 16 | -------------------------------------------------------------------------------- /test/data_storage.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include "quicr/detail/data_storage.h" 7 | 8 | TEST_CASE("DataStorage Construct") 9 | { 10 | CHECK_NOTHROW(quicr::DataStorage<>::Create()); 11 | 12 | auto storage = quicr::DataStorage<>::Create(); 13 | CHECK_NOTHROW(quicr::DataStorage<>::Create()); 14 | } 15 | 16 | TEST_CASE("DataStorage Push") 17 | { 18 | auto buffer = quicr::DataStorage<>::Create(); 19 | 20 | uint64_t value = 0; 21 | auto bytes = quicr::AsBytes(value); 22 | CHECK_NOTHROW(buffer->Push(bytes)); 23 | } 24 | 25 | TEST_CASE("DataStorage Read") 26 | { 27 | auto buffer = quicr::DataStorage<>::Create(); 28 | 29 | uint64_t value = 0x0102030405060708; 30 | CHECK_NOTHROW(buffer->Push(quicr::AsBytes(value))); 31 | 32 | std::vector v(buffer->begin(), buffer->end()); 33 | 34 | CHECK_EQ(v.size(), 8); 35 | CHECK_EQ(v, std::vector{ 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 }); 36 | } 37 | 38 | TEST_CASE("DataStorage Multiples") 39 | { 40 | auto buffer = quicr::DataStorage<>::Create(); 41 | 42 | std::string s1 = "one"; 43 | std::string s2 = " two"; 44 | std::string s3 = " three"; 45 | 46 | buffer->Push(quicr::AsBytes(s1)); 47 | buffer->Push(quicr::AsBytes(s2)); 48 | buffer->Push(quicr::AsBytes(s3)); 49 | 50 | std::vector v(buffer->begin(), buffer->end()); 51 | CHECK_EQ(v.size(), s1.size() + s2.size() + s3.size()); 52 | CHECK_EQ(v.at(5), 'w'); 53 | CHECK_EQ(std::string{ buffer->begin(), buffer->end() }, "one two three"); 54 | 55 | auto buffer_view = quicr::DataStorageDynView(buffer, 1, 11); 56 | std::vector view_v(buffer_view.begin(), buffer_view.end()); 57 | CHECK_EQ(view_v.size(), 10); 58 | CHECK_EQ(view_v.at(4), 'w'); 59 | CHECK_EQ(std::string{ buffer_view.begin(), buffer_view.end() }, "ne two thr"); 60 | 61 | auto buffer_subview = buffer_view.Subspan(3); 62 | std::vector subview_v(buffer_subview.begin(), buffer_subview.end()); 63 | CHECK_EQ(subview_v.size(), s1.size() + s2.size() + s3.size() - 3); 64 | CHECK_EQ(subview_v.at(1), 't'); 65 | CHECK_EQ(std::string{ buffer_subview.begin(), buffer_subview.end() }, " two three"); 66 | } 67 | 68 | TEST_CASE("DataStorage Add and Remove") 69 | { 70 | auto buffer = quicr::DataStorage<>::Create(); 71 | 72 | std::string s1 = "one"; 73 | std::string s2 = " two"; 74 | std::string s3 = " three"; 75 | 76 | buffer->Push(quicr::AsBytes(s1)); 77 | buffer->Push(quicr::AsBytes(s2)); 78 | 79 | const auto buf_view = quicr::DataStorageDynView(buffer); 80 | CHECK_EQ(buf_view.size(), buffer->size()); 81 | 82 | buffer->Push(quicr::AsBytes(s3)); 83 | 84 | CHECK_EQ(buf_view.size(), buffer->size()); 85 | 86 | auto size_before_erase = buffer->size(); 87 | 88 | // Shouldn't remove anything since 2 is less than first element 89 | buffer->EraseFront(2); 90 | CHECK_EQ(buffer->size(), size_before_erase); 91 | 92 | // Should remove the first element 93 | buffer->EraseFront(3); 94 | CHECK_EQ(buffer->size(), size_before_erase - 3); 95 | 96 | // Should remove the next two elements, but not the third 97 | buffer->Push(quicr::AsBytes(s1)); 98 | buffer->Push(quicr::AsBytes(s1)); 99 | size_before_erase = buffer->size(); 100 | buffer->EraseFront(11); 101 | 102 | CHECK_EQ(buffer->size(), size_before_erase - 10); 103 | } 104 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 5 | #include 6 | -------------------------------------------------------------------------------- /test/moq_test.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | using namespace quicr; 12 | 13 | TEST_CASE("Track Handler") {} 14 | -------------------------------------------------------------------------------- /test/tick_service.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace var { 8 | auto tick_service = quicr::ThreadedTickService(); 9 | } 10 | 11 | TEST_CASE("TickService milliseconds") 12 | { 13 | constexpr int sleep_time_ms = 3; 14 | 15 | for (int i = 0; i < 10; i++) { 16 | const auto& start_time = std::chrono::steady_clock::now(); 17 | const auto start_ticks = var::tick_service.Milliseconds(); 18 | 19 | std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time_ms)); 20 | 21 | const auto delta_ticks = var::tick_service.Milliseconds() - start_ticks; 22 | const uint64_t delta_time = 23 | std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time).count(); 24 | 25 | // Allow variance difference 26 | CHECK(static_cast(delta_ticks) >= static_cast(delta_time - 12)); 27 | CHECK(delta_ticks <= delta_time + 12); 28 | } 29 | } 30 | 31 | TEST_CASE("TickService microseconds") 32 | { 33 | constexpr int sleep_time_us = 600; 34 | 35 | for (int i = 0; i < 10; i++) { 36 | const auto& start_time = std::chrono::steady_clock::now(); 37 | const auto start_ticks = var::tick_service.Microseconds(); 38 | 39 | std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_us)); 40 | 41 | const auto delta_ticks = var::tick_service.Microseconds() - start_ticks; 42 | const uint64_t delta_time = 43 | std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time).count(); 44 | 45 | // Allow variance difference 46 | CHECK(static_cast(delta_ticks) >= static_cast(delta_time - 12000)); 47 | CHECK(delta_ticks <= delta_time + 12000); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/track_handlers.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class TestPublishTrackHandler : public quicr::PublishTrackHandler 13 | { 14 | TestPublishTrackHandler() 15 | : PublishTrackHandler({ {}, {}, std::nullopt }, quicr::TrackMode::kDatagram, 0, 0) 16 | { 17 | } 18 | 19 | public: 20 | static std::shared_ptr Create() 21 | { 22 | return std::shared_ptr(new TestPublishTrackHandler()); 23 | } 24 | }; 25 | 26 | TEST_CASE("Create Track Handler") 27 | { 28 | CHECK_NOTHROW(quicr::PublishTrackHandler::Create({ {}, {}, std::nullopt }, quicr::TrackMode::kDatagram, 0, 0)); 29 | CHECK_NOTHROW(TestPublishTrackHandler::Create()); 30 | } 31 | -------------------------------------------------------------------------------- /test/track_namespace.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace quicr; 11 | using namespace std::string_literals; 12 | 13 | std::vector 14 | FindTracks(std::span tracks, const TrackNamespace& track) 15 | { 16 | std::vector matching_tracks; 17 | 18 | std::for_each(tracks.begin(), tracks.end(), [&](const auto& t) { 19 | if (track.IsPrefixOf(t)) 20 | matching_tracks.emplace_back(t); 21 | }); 22 | 23 | return matching_tracks; 24 | } 25 | 26 | const std::vector kTracks{ 27 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev1"s, "time1"s }, 28 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev2"s, "time1"s }, 29 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev1"s, "time3"s }, 30 | TrackNamespace{ "example"s, "chat555"s, "user2"s, "dev1"s, "time4"s }, 31 | }; 32 | 33 | TEST_CASE("Hash namespace") 34 | { 35 | TrackNamespace ns{ "example"s, "chat555"s, "user1"s, "dev1"s, "time1"s }; 36 | 37 | auto h = hash({ ns.begin(), ns.end() }); 38 | CHECK_EQ(15211761882639286592ull, h); 39 | 40 | TrackHash th({ ns, {}, std::nullopt }); 41 | CHECK_EQ(h, th.track_namespace_hash); 42 | 43 | auto ns_hash = std::hash{}(ns); 44 | CHECK_EQ(ns_hash, h); 45 | } 46 | 47 | TEST_CASE("Full match") 48 | { 49 | auto matching_tracks = FindTracks(kTracks, TrackNamespace{ "example"s, "chat555"s }); 50 | CHECK_EQ(matching_tracks, kTracks); 51 | } 52 | 53 | TEST_CASE("Partial match (many entries)") 54 | { 55 | const std::vector expected_tracks{ 56 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev1"s, "time1"s }, 57 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev2"s, "time1"s }, 58 | TrackNamespace{ "example"s, "chat555"s, "user1"s, "dev1"s, "time3"s }, 59 | }; 60 | 61 | auto matching_tracks = FindTracks(kTracks, TrackNamespace{ "example"s, "chat555"s, "user1"s }); 62 | CHECK_EQ(matching_tracks, expected_tracks); 63 | } 64 | 65 | TEST_CASE("Partial match (single entry)") 66 | { 67 | const std::vector expected_tracks{ TrackNamespace{ 68 | "example"s, "chat555"s, "user2"s, "dev1"s, "time4"s } }; 69 | 70 | auto matching_tracks = FindTracks(kTracks, TrackNamespace{ "example"s, "chat555"s, "user2"s }); 71 | CHECK_EQ(matching_tracks, expected_tracks); 72 | } 73 | 74 | TEST_CASE("No match") 75 | { 76 | auto matching_tracks = FindTracks(kTracks, TrackNamespace{ "example"s, "chat555"s, "user"s }); 77 | CHECK(matching_tracks.empty()); 78 | } 79 | 80 | TEST_CASE("IsPrefix vs HasPrefix") 81 | { 82 | TrackNamespace long_ns{ "example"s, "chat555"s, "user2"s, "dev1"s, "time4"s }; 83 | TrackNamespace short_ns{ "example"s, "chat555"s, "user2"s }; 84 | 85 | CHECK(short_ns.IsPrefixOf(long_ns)); 86 | CHECK_FALSE(long_ns.IsPrefixOf(short_ns)); 87 | 88 | CHECK(long_ns.HasSamePrefix(short_ns)); 89 | CHECK(short_ns.HasSamePrefix(long_ns)); 90 | } 91 | 92 | TEST_CASE("Find prefix matching map of namespaces") 93 | { 94 | std::map ns_map; 95 | ns_map.emplace(TrackNamespace{ "example"s, "chat1"s, "user1"s, "dev1"s }, "chat-1-user-1-dev-1"); 96 | ns_map.emplace(TrackNamespace{ "example"s, "chat1"s, "user1"s, "dev2"s }, "chat-1-user-1-dev-2"); 97 | ns_map.emplace(TrackNamespace{ "example"s, "chat2"s, "user1"s, "dev1"s }, "chat-2-user-1-dev-1"); 98 | ns_map.emplace(TrackNamespace{ "example"s, "chat2"s, "user2"s, "dev1"s }, "chat-2-user-2-dev-1"); 99 | ns_map.emplace(TrackNamespace{ "example"s, "chat3"s, "user1"s, "dev1"s }, "chat-3-user-1-dev-1"); 100 | 101 | TrackNamespace prefix_ns{ "example"s, "chat2"s }; 102 | int found = 0; 103 | for (auto it = ns_map.lower_bound(prefix_ns); it != ns_map.end(); ++it) { 104 | if (!it->first.HasSamePrefix(prefix_ns)) { 105 | break; 106 | } 107 | found++; 108 | } 109 | 110 | CHECK_EQ(found, 2); 111 | } 112 | -------------------------------------------------------------------------------- /test/uintvar.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright (c) 2024 Cisco Systems 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include 5 | 6 | #include "quicr/detail/uintvar.h" 7 | 8 | #include 9 | 10 | namespace var { 11 | 12 | // integer values for valdiation tests 13 | constexpr uint64_t kValue1Byte = 0x12; 14 | constexpr uint64_t kValue2Byte = 0x1234; 15 | constexpr uint64_t kValue4Byte = 0x123456; 16 | constexpr uint64_t kValue8Byte = 0x123456789; 17 | 18 | // Encoded values of the above for validation tests 19 | const std::vector kValue1ByteEncoded{ 0x12 }; 20 | const std::vector kValue2ByteEncoded{ 0x52, 0x34 }; 21 | const std::vector kValue4ByteEncoded{ 0x80, 0x12, 0x34, 0x56 }; 22 | const std::vector kValue8ByteEncoded = { 0xC0, 0, 0, 0x1, 0x23, 0x45, 0x67, 0x89 }; 23 | } 24 | 25 | TEST_CASE("Check UintVar") 26 | { 27 | CHECK_EQ(sizeof(std::uint64_t), sizeof(quicr::UintVar)); 28 | CHECK(std::is_trivially_copy_constructible_v); 29 | CHECK(std::is_trivially_copy_assignable_v); 30 | CHECK(std::is_trivially_move_constructible_v); 31 | CHECK(std::is_trivially_move_assignable_v); 32 | } 33 | 34 | TEST_CASE("Encode/Decode UintVar Uint64") 35 | { 36 | CHECK_EQ(var::kValue1Byte, uint64_t(quicr::UintVar(var::kValue1Byte))); 37 | CHECK_EQ(var::kValue2Byte, uint64_t(quicr::UintVar(var::kValue2Byte))); 38 | CHECK_EQ(var::kValue4Byte, uint64_t(quicr::UintVar(var::kValue4Byte))); 39 | CHECK_EQ(var::kValue8Byte, uint64_t(quicr::UintVar(var::kValue8Byte))); 40 | } 41 | 42 | TEST_CASE("Encode/Decode UintVar Bytes") 43 | { 44 | CHECK_EQ(var::kValue1Byte, uint64_t(quicr::UintVar(std::span{ quicr::UintVar(var::kValue1Byte) }))); 45 | CHECK_EQ(var::kValue2Byte, uint64_t(quicr::UintVar(std::span{ quicr::UintVar(var::kValue2Byte) }))); 46 | CHECK_EQ(var::kValue4Byte, uint64_t(quicr::UintVar(std::span{ quicr::UintVar(var::kValue4Byte) }))); 47 | CHECK_EQ(var::kValue8Byte, uint64_t(quicr::UintVar(std::span{ quicr::UintVar(var::kValue8Byte) }))); 48 | } 49 | 50 | TEST_CASE("Length of UintVar") 51 | { 52 | CHECK_EQ(1, quicr::UintVar(var::kValue1Byte).Size()); 53 | CHECK_EQ(2, quicr::UintVar(var::kValue2Byte).Size()); 54 | CHECK_EQ(4, quicr::UintVar(var::kValue4Byte).Size()); 55 | CHECK_EQ(8, quicr::UintVar(var::kValue8Byte).Size()); 56 | 57 | CHECK_EQ(1, quicr::UintVar::Size(*quicr::UintVar(var::kValue1Byte).begin())); 58 | CHECK_EQ(2, quicr::UintVar::Size(*quicr::UintVar(var::kValue2Byte).begin())); 59 | CHECK_EQ(4, quicr::UintVar::Size(*quicr::UintVar(var::kValue4Byte).begin())); 60 | CHECK_EQ(8, quicr::UintVar::Size(*quicr::UintVar(var::kValue8Byte).begin())); 61 | } 62 | 63 | TEST_CASE("Validate UintVar from Known UintVar Bytes") 64 | { 65 | const auto v_one_byte = quicr::UintVar(var::kValue1Byte); 66 | const auto v_two_byte = quicr::UintVar(var::kValue2Byte); 67 | const auto v_four_byte = quicr::UintVar(var::kValue4Byte); 68 | const auto v_eight_byte = quicr::UintVar(var::kValue8Byte); 69 | 70 | CHECK_EQ(var::kValue1Byte, uint64_t(quicr::UintVar(var::kValue1ByteEncoded))); 71 | CHECK_EQ(var::kValue2Byte, uint64_t(quicr::UintVar(var::kValue2ByteEncoded))); 72 | CHECK_EQ(var::kValue4Byte, uint64_t(quicr::UintVar(var::kValue4ByteEncoded))); 73 | CHECK_EQ(var::kValue8Byte, uint64_t(quicr::UintVar(var::kValue8ByteEncoded))); 74 | 75 | CHECK(std::vector(v_one_byte.begin(), v_one_byte.end()) == var::kValue1ByteEncoded); 76 | CHECK(std::vector(v_two_byte.begin(), v_two_byte.end()) == var::kValue2ByteEncoded); 77 | CHECK(std::vector(v_four_byte.begin(), v_four_byte.end()) == var::kValue4ByteEncoded); 78 | CHECK(std::vector(v_eight_byte.begin(), v_eight_byte.end()) == var::kValue8ByteEncoded); 79 | } 80 | 81 | TEST_CASE("UintVar Invalid Construction") 82 | { 83 | CHECK_THROWS(quicr::UintVar(std::numeric_limits::max())); 84 | CHECK_THROWS(quicr::UintVar(std::vector{})); 85 | CHECK_THROWS(quicr::UintVar(std::vector{ 0xFF, 0xFF })); 86 | } 87 | -------------------------------------------------------------------------------- /tools/draft_parser/README.md: -------------------------------------------------------------------------------- 1 | ## Media Over Quic Transport (MOQT) Draft: Parser / Code Generator 2 | 3 | This is a simple parser and code generator for the Media Over Quic Transport Draft. It is written in Python and uses regular expressions to parse the draft. The code is generated using Jinja2 templates. 4 | 5 | ## Running the Parser 6 | 7 | To run the parser, you will need to have Python 3 installed. The requirements.txt lists any required modules. To install you will probably want to create a virtual environment and install the requirements: 8 | 9 | ```bash 10 | python3 -m venv .venv 11 | source .venv/bin/activate 12 | pip install -r requirements.txt 13 | ``` 14 | 15 | You can run the parser by executing the following command: 16 | 17 | ```bash 18 | python3 main.py ./drafts/moq_transport_draft_v8.txt ctrl_messages 19 | ``` 20 | 21 | The first argument is the path to the draft file, and the second argument is the name of the output source file names (without the extension). The parser will generate a {source}.cpp and {source.h} files in the current directory. 22 | 23 | ## Parsing and Code Generation 24 | 25 | ### Draft Requirements 26 | 27 | The parser searches for MOQT controll messages. These messages begin with text that is followed by "Message {" and end with "}". The parser will extract the message name and the fields in the message. All message names and field names are expected to be text words beginning with a capital letter. 28 | 29 | ### Example: CLIENT_SETUP Message 30 | 31 | The following is an example of a CLIENT_SETUP message in the draft: 32 | 33 | ``` 34 | CLIENT_SETUP Message { 35 | Type (i) = 0x40, 36 | Length (i), 37 | Number of Supported Versions (i), 38 | Supported Versions (i) ..., 39 | Number of Parameters (i), 40 | Setup Parameters (..) ..., 41 | } 42 | ``` 43 | 44 | The parser reads this message from the draft and gerates a MessageSpec object that contains information about the message fields. The code gerator uses thie MessageSpec object with Jinja2 templates to generate the C++ code for the message. 45 | 46 | For the CLIENT_SETUP message the C++ code generated is as follows: 47 | 48 | Header: 49 | ```cpp 50 | struct ClientSetup 51 | { 52 | SupportedVersions supported_versions; 53 | SetupParameters setup_parameters; 54 | }; 55 | ``` 56 | 57 | Source: 58 | ```cpp 59 | Bytes& operator<<(Bytes& buffer, const ClientSetup& msg) 60 | { 61 | Bytes payload; 62 | 63 | // fill out payload 64 | payload << msg.supported_versions; // (i) ... << SupportedVersions 65 | payload << msg.setup_parameters; // (..) ... << SetupParameters 66 | 67 | // fill out buffer 68 | buffer << static_cast(ControlMessageType::kClientSetup); 69 | buffer << payload; 70 | return buffer; 71 | } 72 | 73 | BytesSpan operator>>(BytesSpan buffer, ClientSetup& msg) 74 | { 75 | buffer = buffer >> msg.supported_versions; // (i) ... >> SupportedVersions 76 | buffer = buffer >> msg.setup_parameters; // (..) ... >> SetupParameters 77 | return buffer; 78 | } 79 | ``` 80 | 81 | For this project the Type and Length fields are ignored since they are decoded in a different way. Also, since both Supported Version and Setup Parameters are defined as repeatable (...) the code generator automatically generates these fields as vectors that automatically include streaming length fields.So, the "Number of..." fields are ignored. 82 | 83 | The current project uses streaming for all encoding and decoding of MOQT control messages. This is reflected in the code generator as stream operators for the broader message as well as each of the message fields. 84 | 85 | ### Optional Fields 86 | 87 | The parser supports parsing optional fiels in the draft. These fields are defined where fields are enclosed in square brackets. For example: 88 | 89 | ``` 90 | SUBSCRIBE Message { 91 | Type (i) = 0x3, 92 | Length (i), 93 | Subscribe ID (i), 94 | Track Alias (i), 95 | Track Namespace (tuple), 96 | Track Name Length (i), 97 | Track Name (..), 98 | Subscriber Priority (8), 99 | Group Order (8), 100 | Filter Type (i), 101 | [Start Group (i), 102 | Start Object (i)], 103 | [End Group (i)], 104 | Number of Parameters (i), 105 | Subscribe Parameters (..) ... 106 | } 107 | ``` 108 | 109 | Here is the generated header file definition for the SUBSCRIBE message: 110 | 111 | ```cpp 112 | struct Subscribe 113 | { 114 | struct Group_0 { 115 | StartGroup start_group; 116 | StartObject start_object; 117 | }; 118 | struct Group_1 { 119 | EndGroup end_group; 120 | }; 121 | SubscribeID subscribe_id; 122 | TrackAlias track_alias; 123 | TrackNamespace track_namespace; 124 | TrackName track_name; 125 | SubscriberPriority subscriber_priority; 126 | GroupOrder group_order; 127 | FilterType filter_type; 128 | std::function optional_group_0_cb; 129 | std::optional group_0; 130 | std::functionoptional_group_1_cb; 131 | std::optional group_1; 132 | SubscribeParameters subscribe_parameters; 133 | }; 134 | ``` 135 | 136 | Notice how the optional fields from the original specification definition are mapped to nested structures. These nested structures are then uses as types for `std::optional` fields in the main message structure. Also note that the code generator also includes `std::function` fields. These fields should be set to callback methods to determine when and if optional fields are present in the message. 137 | 138 | 139 | ## Future Work 140 | 141 | There is still work to be done. The MOQT draft is still in development and the parser and code generator will need to be updated as the draft changes. Also, the parser and code generator could be improved to handle more complex message structures and to generate more efficient code. -------------------------------------------------------------------------------- /tools/draft_parser/drafts/new_group_addendum.txt: -------------------------------------------------------------------------------- 1 | Addendum: 2 | 3 | New Group Request 4 | 5 | NEW_GROUP_REQUEST Message { 6 | Type (i) = 0x42, 7 | Length (i), 8 | Request ID (i), 9 | Track Alias (i) 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/draft_parser/main.py: -------------------------------------------------------------------------------- 1 | """MOQ Draft Parser""" 2 | 3 | import sys 4 | from moqt_parser import ProtocolMessageParser, CodeGenerator # , TableParser 5 | 6 | 7 | def main(rfc_filename, output_name): 8 | """Main.""" 9 | 10 | type_map = { 11 | "i": "std::uint64_t", # variable-length integer 12 | "8": "std::uint8_t", 13 | "16": "std::uint16_t", 14 | "32": "std::uint32_t", 15 | "64": "std::uint64_t", 16 | "Start Group": "quicr::messages::GroupId", 17 | "End Group": "quicr::messages::GroupId", 18 | "Start Object": "quicr::messages::ObjectId", 19 | "End Object": "quicr::messages::ObjectId", 20 | "Track Namespace": "quicr::TrackNamespace", 21 | "Track Name": "quicr::Bytes", 22 | "Track Namespace Prefix": "quicr::TrackNamespace", 23 | "Subscribe Parameters": "quicr::messages::Parameter", 24 | "Setup Parameters": "quicr::messages::Parameter", 25 | "New Session URI": "quicr::Bytes", 26 | "Parameters": "quicr::messages::Parameter", 27 | "Reason Phrase": "quicr::Bytes", 28 | "Filter Type": "quicr::messages::FilterType", 29 | "Group Order": "quicr::messages::GroupOrder", 30 | "Fetch Type": "quicr::messages::FetchType", 31 | "SubscribeDone::StatusCode": "quicr::messages::SubscribeDoneStatusCode", 32 | "SubscribeError::ErrorCode": "quicr::messages::SubscribeErrorCode", 33 | "AnnounceError::ErrorCode": "quicr::messages::AnnounceErrorCode", 34 | "AnnounceCancel::ErrorCode": "quicr::messages::AnnounceErrorCode", 35 | "SubscribeAnnouncesError::ErrorCode": "quicr::messages::SubscribeAnnouncesErrorCode", 36 | "FetchError::ErrorCode": "quicr::messages::FetchErrorCode", 37 | "Start Location": "quicr::messages::Location", 38 | "End Location": "quicr::messages::Location", 39 | "Largest Location": "quicr::messages::Location", 40 | } 41 | 42 | field_discards = ["Type", "Length"] 43 | 44 | parser = ProtocolMessageParser(type_map) 45 | # table_parser = TableParser() 46 | generator = CodeGenerator("cpp") 47 | messages = [] 48 | 49 | with open(rfc_filename, "r", encoding="utf8") as rfc_file: 50 | spec = rfc_file.read() 51 | messages = parser.parse_messages(spec) 52 | 53 | # tables = table_parser.parse_tables(spec) 54 | # print(tables) 55 | # for table in tables: 56 | # print(table) 57 | 58 | using_map = {} 59 | repeat_using_map = {} 60 | for message in messages: 61 | for field in message.fields: 62 | if field.group_fields: 63 | for group_field in field.group_fields: 64 | if group_field.cpp_using_name is not None: 65 | using_map[group_field.cpp_using_name] = group_field 66 | else: 67 | print("gropu_file.cpp_using_name is None") 68 | 69 | # if field.cpp_using_name is not None: 70 | if field.spec_name not in field_discards: 71 | if field.is_optional is False: 72 | using_map[field.cpp_using_name] = field 73 | 74 | for using in using_map: 75 | field = using_map[using] 76 | if field.is_repeated: 77 | repeat_using_map[using_map[using].cpp_using_type] = field 78 | 79 | if len(messages) > 0: 80 | with open(f"{output_name}.h", "w", encoding="utf8") as header_file: 81 | output = generator.generate_header( 82 | messages, using_map, repeat_using_map, field_discards 83 | ) 84 | print(output, file=header_file) 85 | with open( 86 | f"{output_name}.cpp", "w", encoding="utf8" 87 | ) as source_file: 88 | output = generator.generate_source( 89 | messages, using_map, repeat_using_map, field_discards 90 | ) 91 | print(output, file=source_file) 92 | else: 93 | print("Protocol parser returned empty parsed messsages list.") 94 | 95 | 96 | if __name__ == "__main__": 97 | if len(sys.argv) != 3: 98 | print("Usage error - rfc filename required:") 99 | print(f" {sys.argv[0]} rfc_filename output_name") 100 | else: 101 | main(sys.argv[1], sys.argv[2]) 102 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/__init__.py: -------------------------------------------------------------------------------- 1 | # moqt_parser/__init__.py 2 | from .parsers import ProtocolMessageParser 3 | from .parsers import TableParser 4 | from .code_generator import CodeGenerator 5 | 6 | __version__ = '0.1.0' 7 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/message_spec.py: -------------------------------------------------------------------------------- 1 | """Define MessageSpec Class and Field Class""" 2 | 3 | from typing import List 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class Field: 9 | """Messgae field class.""" 10 | 11 | name: str 12 | field_type: str 13 | spec_name: str 14 | spec_type: str = None 15 | cpp_type: str = None 16 | cpp_using_type: str = None 17 | cpp_using_name: str = None 18 | length: str = None 19 | default_value: str = None 20 | is_optional: bool = False 21 | is_repeated: bool = False 22 | is_variable_length: bool = False 23 | variable_length_size_cpp_using_type = None 24 | group_name: str = None 25 | group_fields: List[("Field")] = None 26 | 27 | 28 | class MessageSpec: 29 | """Messgae specification class.""" 30 | 31 | def __init__( 32 | self, 33 | name: str, 34 | spec_name: str, 35 | message_type: str, 36 | message_enum: int, 37 | fields: List[Field], 38 | optional_groups: List[str], 39 | ): 40 | """Protocol message spec constructor.""" 41 | self.name = name 42 | self.spec_name = spec_name 43 | self.message_type = message_type 44 | self.message_enum = message_enum 45 | # self.spec_name = name 46 | self.fields = fields 47 | self.optional_groups = optional_groups 48 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/table_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class TableParser: 4 | def __init__(self): 5 | # This can be used to store any additional configuration if needed. 6 | pass 7 | 8 | def parse_tables(self, text): 9 | # Regular expressions to match the table structure and extract data 10 | table_header_pattern = r'(\+======+\+.+?\+.======\+.+?\+.======+)' 11 | row_pattern = r'\| (\S+)\|\s*(.+?)\s*\|\' 12 | 13 | # Extract all tables from the text 14 | matches = re.findall(table_header_pattern, text) 15 | 16 | # Convert each match into a dictionary of rows 17 | tables = [] 18 | for match in matches: 19 | table_rows = re.findall(row_pattern, match.replace('|', '')) 20 | header = [item.strip() for item in match.split(' +')[1].split(' +') if item.strip()] 21 | 22 | current_table = {} 23 | for row in table_rows: 24 | if not row: # Skip empty lines 25 | continue 26 | row_data = dict(zip(header, (item.strip().replace(' ', '_') for item in row.split(' | ')))) 27 | current_table[row_data['Code']] = row_data 28 | 29 | tables.append(current_table) 30 | 31 | return tables 32 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Header_Begin_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace quicr::messages { 8 | 9 | 10 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Header_End_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | {% set unique_types = [] %} 2 | {% for key in using_map if using_map[key].cpp_using_type not in unique_types %} 3 | {% if using_map[key].is_repeated %} 4 | {{ unique_types.append(using_map[key].cpp_using_type) or "" }} 5 | {% endif %} 6 | {% endfor %} 7 | {% for utype in unique_types %} 8 | Bytes& operator<<(Bytes& buffer, const {{utype}}& vec); 9 | BytesSpan operator>>(BytesSpan buffer, {{utype}}& vec); 10 | 11 | {% endfor %} 12 | 13 | /** 14 | * @brief Subscribe attributes 15 | */ 16 | struct SubscribeAttributes 17 | { 18 | uint8_t priority; ///< Subscriber priority 19 | GroupOrder group_order; ///< Subscriber group order 20 | }; 21 | 22 | /** 23 | * @brief Fetch attributes 24 | */ 25 | struct FetchAttributes 26 | { 27 | uint8_t priority; ///< Fetch priority 28 | GroupOrder group_order; ///< Fetch group order 29 | StartGroup start_group; ///< Fetch starting group in range 30 | StartObject start_object; ///< Fetch starting object in group 31 | EndGroup end_group; ///< Fetch final group in range 32 | std::optional end_object; ///< Fetch final object in group 33 | }; 34 | 35 | Bytes& operator<<(Bytes& buffer, ControlMessageType message_type); 36 | BytesSpan operator>>(BytesSpan buffer, TrackNamespace& msg); 37 | Bytes& operator<<(Bytes& buffer, const TrackNamespace& msg); 38 | 39 | } // namespace 40 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Header_Enums_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | // enums 3 | enum class ControlMessageType : uint64_t 4 | { 5 | {% for message in messages %} 6 | k{{message.message_type}} = 0x{{'%0x' % message.message_enum}}, 7 | {% endfor %} 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Header_Structs_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief {{message.name}} 3 | */ 4 | struct {{ message.name }} 5 | { 6 | {% if message.optional_groups | length > 0 %} 7 | public: 8 | // Optional Groups 9 | {% for group_name in message.optional_groups %} 10 | struct {{group_name}} { 11 | {% for field in message.optional_groups[group_name] %} 12 | {{ field.cpp_using_name }} {{ field.name }}; 13 | {% endfor %} 14 | }; 15 | {% endfor %} 16 | {% endif %} 17 | 18 | public: 19 | {% if message.optional_groups | length == 0 %} 20 | // Default constructor 21 | {{message.name}} () {} 22 | {% else %} 23 | // Have optionals - delete default constructor 24 | {{message.name}} () = delete; 25 | {% endif %} 26 | 27 | // All fields constructor 28 | {{message.name}} ( 29 | {% for field in message.fields %} 30 | {% if field.spec_name not in field_discards %} 31 | {% if field.is_optional %} 32 | std::function {{field.name}}_cb, 33 | std::optional<{{ field.cpp_using_name }}> {{ field.name }}{{ "):" if loop.last else "," }} 34 | {% else %} 35 | {{ field.cpp_using_name }} {{ field.name }}{{ "):" if loop.last else "," }} 36 | {% endif %} 37 | {% endif %} 38 | {% endfor %} 39 | {% for field in message.fields %} 40 | {% if field.name != "type" and field.name != "length" %} 41 | {% if field.is_optional %} 42 | {{field.name}}_cb({{field.name}}_cb), 43 | {% endif %} 44 | {{ field.name }}({{ field.name }}){{ "," if not loop.last else ""}} 45 | {% endif %} 46 | {% endfor %} 47 | {} 48 | 49 | {% set ns = namespace(count=message.optional_groups|length ) %} 50 | {% if ns.count > 0 %} 51 | // Optional callback constructor 52 | 53 | {{message.name}} ( 54 | {%for field in message.fields %} 55 | {% if field.spec_name not in field_discards %} 56 | {% if field.is_optional %} 57 | {% set ns.count = ns.count -1 %} 58 | std::function {{field.name}}_cb{{ "," if ns.count > 0}} 59 | {% endif %} 60 | {% endif %} 61 | {% endfor %} 62 | ); 63 | {% endif %} 64 | 65 | public: 66 | {% for field in message.fields %} 67 | {% if field.spec_name not in field_discards %} 68 | {% if field.is_optional %} 69 | std::function {{field.name}}_cb; 70 | std::optional<{{ field.cpp_using_name }}> {{ field.name }}; 71 | {% else %} 72 | {{ field.cpp_using_name }} {{ field.name }}; 73 | {% endif %} 74 | {% endif %} 75 | {% endfor %} 76 | }; 77 | 78 | Bytes& operator<<(Bytes& buffer, const {{ message.name }}& msg); 79 | BytesSpan operator>>(BytesSpan buffer, {{ message.name }}& msg); 80 | 81 | {% for group_name in message.optional_groups %} 82 | Bytes& operator<<(Bytes& buffer, const std::optional<{{message.name}}::{{group_name}}>& grp); 83 | BytesSpan operator>>(BytesSpan buffer, std::optional<{{message.name}}::{{group_name}}>& grp); 84 | 85 | {% endfor %} 86 | 87 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Header_Using_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | // usings 3 | {% for key in using_map %} 4 | {% if using_map[key] %} 5 | {% set field = using_map[key] %} 6 | {% if field.cpp_using_type %} 7 | using {{field.cpp_using_name}} = {{field.cpp_using_type}}; 8 | {% else %} 9 | using {{field.cpp_using_name}} = {{field.cpp_using_type}}; 10 | {% endif %} 11 | {% endif %} 12 | {% endfor %} 13 | 14 | {# ALREAY PART OF Header_End... 15 | // stream vectors 16 | {% for cpp_using_type in repeat_map %} 17 | Bytes& operator<<(Bytes& buffer, const {{cpp_using_type}}& vec); 18 | BytesSpan operator>>(BytesSpan buffer, {{cpp_using_type}}& vec); 19 | {% endfor %} 20 | #} -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Source_Begin_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | #include "quicr/detail/messages.h" 3 | 4 | namespace quicr::messages { 5 | 6 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Source_End_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bytes &operator<<(Bytes &buffer, ControlMessageType message_type) 4 | { 5 | UintVar varint = static_cast(message_type); 6 | buffer << varint; 7 | return buffer; 8 | } 9 | 10 | Bytes& operator<<(Bytes& buffer, const TrackNamespace& ns) 11 | { 12 | const auto& entries = ns.GetEntries(); 13 | 14 | buffer << UintVar(entries.size()); 15 | for (const auto& entry : entries) { 16 | buffer << entry; 17 | } 18 | 19 | return buffer; 20 | } 21 | 22 | BytesSpan operator>>(BytesSpan buffer, TrackNamespace& msg) 23 | { 24 | uint64_t size = 0; 25 | buffer = buffer >> size; 26 | 27 | std::vector entries(size); 28 | for (auto& entry : entries) { 29 | 30 | buffer = buffer >> entry; 31 | } 32 | 33 | msg = TrackNamespace{ entries }; 34 | 35 | return buffer; 36 | } 37 | 38 | } // namespace 39 | 40 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Source_Structs_tmple.jinja2: -------------------------------------------------------------------------------- 1 | {% set ns = namespace(count=message.optional_groups|length ) %} 2 | {% if ns.count > 0 %} 3 | /* 4 | * {{message.name}} stream in constructor 5 | */ 6 | {{message.name}}::{{message.name}}( 7 | {%for field in message.fields %} 8 | {% if field.spec_name not in field_discards %} 9 | {% if field.is_optional %} 10 | {% set ns.count = ns.count - 1 %} 11 | std::function {{field.name}}_cb{{"," if ns.count > 0 }} 12 | {% endif %} 13 | {% endif %} 14 | {% endfor %} 15 | ){{ ":" if message.optional_groups|length > 0 }} 16 | {% set ns.count = message.optional_groups|length %} 17 | {%for field in message.fields %} 18 | {% if field.spec_name not in field_discards %} 19 | {% if field.is_optional %} 20 | {% set ns.count = ns.count - 1 %} 21 | {{field.name}}_cb({{field.name}}_cb){{"," if ns.count > 0 }} 22 | {% endif %} 23 | {% endif %} 24 | {% endfor %} 25 | { 26 | } 27 | {% endif %} 28 | 29 | /* 30 | * {{message.name}} stream in 31 | */ 32 | BytesSpan operator>>(BytesSpan buffer, {{message.name}}& msg) 33 | { 34 | {% for field in message.fields %} 35 | {% if field.spec_name not in field_discards %} 36 | {% if field.is_repeated %} 37 | {% set repeated = "..." %} 38 | {% endif %} 39 | {% if field.is_optional %} 40 | if (msg.{{field.name}}_cb) { msg.{{field.name}}_cb(msg); } 41 | buffer = buffer >> msg.{{field.name}}; // ({{field.spec_type}}) {{repeated}} >> {{field.cpp_using_name}} 42 | {% else %} 43 | buffer = buffer >> msg.{{field.name}}; // ({{field.spec_type}}) {{repeated}} >> {{field.cpp_using_name}} 44 | {% endif %} 45 | {% endif %} 46 | {% endfor %} 47 | return buffer; 48 | } 49 | 50 | /* 51 | * {{message.name}} stream out 52 | */ 53 | Bytes& operator<<(Bytes& buffer, const {{message.name}}& msg) 54 | { 55 | Bytes payload; 56 | // fill out payload 57 | {% for field in message.fields %} 58 | {% if field.spec_name not in field_discards %} 59 | {% if field.is_repeated %} 60 | {% set repeated = "..." %} 61 | {% endif %} 62 | {% if field.is_variable_length %} 63 | {% endif %} 64 | payload << msg.{{field.name}}; // ({{field.spec_type}}) {{repeated}} << {{field.cpp_using_name}} 65 | {% endif %} 66 | {% endfor %} 67 | 68 | // fill out buffer 69 | ControlMessage message; 70 | message.type = static_cast(ControlMessageType::k{{message.message_type}}); 71 | message.payload = payload; 72 | buffer << message; 73 | return buffer; 74 | } 75 | 76 | {% for group_name in message.optional_groups %} 77 | BytesSpan operator>>(BytesSpan buffer, std::optional<{{message.name}}::{{group_name}}>& grp) 78 | { 79 | if (grp.has_value()) { 80 | {% for field in message.optional_groups[group_name] %} 81 | buffer = buffer >> grp->{{field.name}}; // ({{field.spec_type}}) >> {{field.cpp_using_name}} 82 | {% endfor %} 83 | } 84 | return buffer; 85 | } 86 | 87 | Bytes& operator<<(Bytes& buffer, const std::optional<{{message.name}}::{{group_name}}>& grp) 88 | { 89 | if (grp.has_value()) { 90 | {% for field in message.optional_groups[group_name] %} 91 | buffer << grp->{{field.name}}; // ({{field.spec_type}}) << {{field.cpp_using_name}} 92 | {% endfor %} 93 | } 94 | return buffer; 95 | } 96 | {% endfor %} -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/MessageSpec_Source_Using_templ.jinja2: -------------------------------------------------------------------------------- 1 | // usings 2 | {% for cpp_using_type in repeat_map %} 3 | {% if repeat_map[cpp_using_type] %} 4 | {% set field = repeat_map[cpp_using_type] %} 5 | Bytes& operator<<(Bytes& buffer, const {{field.cpp_using_type}}& vec) 6 | { 7 | // write vector size 8 | buffer << static_cast<{{field.variable_length_size_cpp_using_type}}>(vec.size()); 9 | 10 | // write elements of vector 11 | for (const auto& item : vec) { 12 | buffer << item; 13 | } 14 | 15 | return buffer; 16 | } 17 | 18 | BytesSpan operator>>(BytesSpan buffer, {{field.cpp_using_type}}& vec) 19 | { 20 | {{field.variable_length_size_cpp_using_type}} size = 0; 21 | buffer = buffer >> size; 22 | 23 | for (uint64_t i=0; i> item; 26 | vec.push_back(item); 27 | } 28 | 29 | return buffer; 30 | } 31 | {% endif %} 32 | {% endfor %} -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Header_Begin_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "quicr/detail/transport.h" 5 | #include "quicr/detail/messages.h" 6 | 7 | #include 8 | 9 | namespace quicr { -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Header_End_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | } // namespace 3 | 4 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Header_Structs_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @brief Send{{message.name}} 4 | */ 5 | void Send{{message.name}}( 6 | quicr::Transport& transport, 7 | const quicr::Transport::ConnectionContext& conn_ctx, 8 | {% for field in message.fields %} 9 | {% if field.name != "type" and field.name != "length" %} 10 | {% if field.is_optional %} 11 | std::optional {{ field.name }}{{ "," if not loop.last else ");" }} 12 | {% else %} 13 | messages::{{ field.cpp_using_name }} {{ field.name }}{{ "," if not loop.last else ");" }} 14 | {% endif %} 15 | {% endif %} 16 | {% endfor %} 17 | 18 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Source_Begin_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | #include "quicr/detail/transport_messages.h" 3 | 4 | namespace quicr::messages { 5 | 6 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Source_End_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | } // namespace 4 | -------------------------------------------------------------------------------- /tools/draft_parser/moqt_parser/templates/cpp/Transport_Source_Structs_tmpl.jinja2: -------------------------------------------------------------------------------- 1 | 2 | {% set lower_name = message.spec_name.lower() %} 3 | /** 4 | * @brief Send{{message.name}} 5 | */ 6 | void Send{{message.name}}( 7 | quicr::Transport& transport, 8 | const quicr::Transport::ConnectionContext& conn_ctx, 9 | {% for field in message.fields %} 10 | {% if field.name != "type" and field.name != "length" %} 11 | {% if field.is_optional %} 12 | std::optional<{{ field.cpp_using_name }}> {{ field.name }}{{ "," if not loop.last else "" }} 13 | {% else %} 14 | {{ field.cpp_using_name }} {{ field.name }}{{ "," if not loop.last else "" }} 15 | {% endif %} 16 | {% endif %} 17 | {% endfor %} 18 | ) 19 | { 20 | {{message.name}} {{lower_name}} = { 21 | {% for field in message.fields %} 22 | {% if field.name != "type" and field.name != "length" %} 23 | .{{field.name}} = {{ field.name -}}{{ "," if not loop.last }} 24 | {% endif %} 25 | {% endfor %} 26 | }; 27 | 28 | 29 | Bytes buffer; 30 | buffer.reserve(sizeof({{message.name}})); 31 | buffer << {{lower_name}}; 32 | 33 | transport.SendCtrlMsg(conn_ctx, buffer); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /tools/draft_parser/requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==3.1.6 2 | --------------------------------------------------------------------------------